This Rmarkdown file assesses the output of CheckV, DeepVirFinder, Kaiju, VIBRANT, VirSorter, and VirSorter2 on multiple training sets of microbial DNA, primarily from NCBI. Created from fungal, viral, bacterial, archeael, protist, and plasmid DNA sequences
Please reach out to James Riddell (riddell.26@buckeyemail.osu.edu) or Bridget Hegarty (beh53@case.edu) regarding any issues, or open an issue on github.
library(ggplot2)
There were 30 warnings (use warnings() to see them)
library(plyr)
library(reshape2)
library(viridis)
library(tidyr)
library(dplyr)
library(readr)
library(data.table)
library(pROC)
Type 'citation("pROC")' for a citation.
Attaching package: ‘pROC’
The following objects are masked from ‘package:stats’:
cov, smooth, var
library("stringr")
Import the file that combines the results from each of the tools from running “combining_tool_output.Rmd”:
viruses <- read_tsv("../IntermediaryFiles/viral_tools_combined.tsv")
── Column specification ─────────────────────────────────────────────────────────────────────────
cols(
.default = col_double(),
seqtype = col_character(),
contig = col_character(),
checkv_provirus = col_character(),
checkv_quality = col_character(),
method.x = col_character(),
Classified = col_character(),
IDs_all = col_character(),
Seq = col_character(),
Kaiju_Viral = col_character(),
Kingdom = col_character(),
type = col_character(),
vibrant_quality = col_character(),
method.y = col_character(),
vibrant_prophage = col_character(),
vs2type = col_character(),
max_score_group = col_character(),
provirus = col_logical()
)
ℹ Use `spec()` for the full column specifications.
There were 50 or more warnings (use warnings() to see the first 50)
(this is still the old rules)
This section defines a viralness score “keep_score” based on the tool classifications. A final keep_score above 1 indicates we will keep that sequence and call it viral.
VIBRANT Quality == “High Quality Draft”: +1 Quality == “Medium Quality Draft”: +1 Quality == “Low Quality Draft” & provirus: +0.5
Virsorter2 Viral >= 50: +0.5 Viral >= 0.95: +0.5 RNA >= 0.9: +1 lavidaviridae >= 0.9: +1 NCLDV >= 0.9: +1
Virsorter category == 1,4: +1 category == 2,5: +0.5
DeepVirFinder: Score >= 0.7: +0.5
Tuning - No Viral Signature: Kaiju_viral = “cellular organisms”: -0.5 If host_genes >50 and NOT provirus: -1 If viral_genes == 0 and host_genes >= 1: -1 If 3*viral_genes <= host_genes and NOT provirus: -1 If length > 50,000 and hallmark <=1: -1 If length < 5000 and checkv completeness <= 75: -0.5
Tuning - Viral Signature: Kaiju_viral = “Viruses”: +0.5 If %unknown >= 75 and length < 50000: +0.5 If %viral >= 50: +0.5 Hallmark > 2: +0.5
This script produces visualizations of these combined viral scorings and includes ecological metrics like alpha diversity.
You can decide which combination is appropriate for them and only need use the tools appropriate for your data.
Trying new rule set
this set is based on optimizing for the
getting_viral_set_1 <- function(input_seqs,
There were 50 or more warnings (use warnings() to see the first 50)
include_vibrant=FALSE,
include_virsorter2=FALSE,
include_deepvirfinder=FALSE,
include_tuning_viral=FALSE,
include_tuning_not_viral=FALSE,
include_virsorter=FALSE) {
keep_score <- rep(0, nrow(input_seqs))
if (include_vibrant) {
keep_score[input_seqs$vibrant_quality=="high quality draft"] <- keep_score[input_seqs$vibrant_quality=="high quality draft"] + 1
keep_score[input_seqs$vibrant_quality=="medium quality draft"] <- keep_score[input_seqs$vibrant_quality=="medium quality draft"] + 1
keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$provirus] <- keep_score[input_seqs$vibrant_quality=="low quality draft" & input_seqs$provirus] + 0.5
}
if (include_virsorter2) {
keep_score[input_seqs$max_score>=50] <- keep_score[input_seqs$max_score>=50] + 0.5
keep_score[input_seqs$max_score>=95] <- keep_score[input_seqs$max_score>=95] + 0.5
}
if (include_virsorter) {
keep_score[input_seqs$category==1] <- keep_score[input_seqs$category==1] + 1
keep_score[input_seqs$category==2] <- keep_score[input_seqs$category==2] + 0.5
keep_score[input_seqs$category==3] <- keep_score[input_seqs$category==3] + 0.5
keep_score[input_seqs$category==4] <- keep_score[input_seqs$category==4] + 1
keep_score[input_seqs$category==5] <- keep_score[input_seqs$category==5] + 0.5
keep_score[input_seqs$category==6] <- keep_score[input_seqs$category==6] + 0.5
}
if (include_deepvirfinder) {
# remove if DVF calls it not viral
keep_score[input_seqs$score<=0.7 & input_seqs$pvalue<=0.05] <- keep_score[input_seqs$score<=0.7 & input_seqs$pvalue<=0.05] - 1
# add if DVF calls viral
keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.9 & input_seqs$checkv_length<20000] + 0.5
keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] <- keep_score[input_seqs$score>=0.7 & input_seqs$checkv_length<20000] + 0.5
}
if (include_tuning_viral) {
# tuning addition
keep_score[input_seqs$hallmark>2] <- keep_score[input_seqs$hallmark>2] + 1
keep_score[input_seqs$Kaiju_Viral=="Viruses"] <- keep_score[input_seqs$Kaiju_Viral=="Viruses"] + 1
keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] <- keep_score[input_seqs$percent_unknown>=75 & input_seqs$checkv_length<50000] + 0.5
keep_score[input_seqs$viral>=50 | input_seqs$percent_viral>=50] <- keep_score[input_seqs$viral>=50 | input_seqs$percent_viral>=50] + 0.5
keep_score[(input_seqs$checkv_completeness>=75 | input_seqs$vibrant_quality=="complete circular") & (input_seqs$viral>=50 | input_seqs$percent_viral>=50)] <- keep_score[(input_seqs$checkv_completeness>=75 | input_seqs$vibrant_quality=="complete circular") & (input_seqs$viral>=50 | input_seqs$percent_viral>=50)] + 0.5
}
if (include_tuning_not_viral) {
# tuning removal
keep_score[input_seqs$checkv_host_genes>50 & !input_seqs$provirus] <- 0
keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] <- keep_score[input_seqs$checkv_viral_genes==0 & input_seqs$checkv_host_genes>=1] - 1
keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & !input_seqs$provirus] <- keep_score[((input_seqs$checkv_viral_genes*3) <= input_seqs$checkv_host_genes) & !input_seqs$provirus] - 1
keep_score[input_seqs$checkv_length>500000 & input_seqs$hallmark<=1] <- 0
keep_score[(input_seqs$checkv_completeness<=75) & input_seqs$checkv_length<=5000] <- keep_score[(input_seqs$checkv_completeness<=75) & input_seqs$checkv_length<=5000] - 0.5
}
return(keep_score)
}
Trying with all of the rules
viruses$keep_score_all_2 <- getting_viral_set_1(viruses,
There were 50 or more warnings (use warnings() to see the first 50)
include_vibrant=TRUE,
include_virsorter2=TRUE,
include_deepvirfinder=TRUE,
include_tuning_viral=TRUE,
include_tuning_not_viral=TRUE,
include_virsorter=TRUE)
viruses$confusion_matrix_all_2 <- "true negative"
viruses$confusion_matrix_all_2[viruses$seqtype=="virus" & viruses$keep_score_all_2<1] <- "false negative"
viruses$confusion_matrix_all_2[viruses$seqtype=="virus" & viruses$keep_score_all_2>=1] <- "true positive"
viruses$confusion_matrix_all_2[viruses$seqtype!="virus" & viruses$keep_score_all_2>=1] <- "false positive"
viruses$accurate_all_2 <- "false"
viruses$accurate_all_2[grep("true", viruses$confusion_matrix_all_2)] <- "true"
accuracy:
length(grep("true", viruses$accurate_all_2))/nrow(viruses)
[1] 0.9321017
There were 50 or more warnings (use warnings() to see the first 50)
precision
length(grep("true positive", viruses$confusion_matrix_all_2))/length(grep("positive", viruses$confusion_matrix_all_2))
[1] 0.6164715
recall
length(grep("true positive", viruses$confusion_matrix_all_2))/(length(grep("true positive", viruses$confusion_matrix_all_2))+length(grep("false negative", viruses$confusion_matrix_all_2)))
[1] 0.9162
There were 27 warnings (use warnings() to see them)
visualizing accuracy by taxa
accurate_by_taxa <- melt(table(viruses$accurate_all_2, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$accurate_all_2, viruses$seqtype, viruses$Index)). In the next version, this warning will become an error.
colnames(accurate_by_taxa) <- c("accurate", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
There were 50 or more warnings (use warnings() to see the first 50)
ggplot(accurate_by_taxa, aes(y=count, x=accurate,
fill=accurate,
color=accurate)) +
geom_boxplot() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(2)), 0.5),
labels=c("false", "true")) +
scale_color_manual(name="",
values = alpha(rev(pal(2)), 1),
labels=c("false", "true")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free")

Assessing performance against the “truth”
note that this is only as accurate as the annotations of the input sequences
this function calculates the precision, recall, and F1 score for each pipeline
assess_performance <- function(seqtype, keep_score) {
There were 50 or more warnings (use warnings() to see the first 50)
truepositive <- rep("not viral", length(seqtype))
truepositive[seqtype=="virus"] <- "viral"
#make confusion matrix
confusion_matrix <- rep("true negative", length(keep_score))
confusion_matrix[truepositive=="viral" & keep_score<=1] <- "false negative"
confusion_matrix[truepositive=="viral" & keep_score>=1] <- "true positive"
confusion_matrix[truepositive=="not viral" & keep_score>=1] <- "false positive"
TP <- table(confusion_matrix)[4]
FP <- table(confusion_matrix)[2]
TN <- table(confusion_matrix)[3]
FN <- table(confusion_matrix)[1]
precision <- TP/(TP+FP)
recall <- TP/(TP+FN)
F1 <- 2*precision*recall/(precision+recall)
MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
auc <- round(auc(truepositive, keep_score),4)
performance <- c(precision, recall, F1, MCC, auc)
names(performance) <- c("precision", "recall", "F1", "MCC", "AUC")
return(performance)
}
combination of tools list
combos_list <- data.frame(toolcombo=rep(0, 64),
There were 50 or more warnings (use warnings() to see the first 50)
tune_not_viral=rep(0, 64),
DVF=rep(0, 64),
tune_viral=rep(0, 64),
VIBRANT=rep(0, 64),
VS=rep(0, 64),
VS2=rep(0, 64))
p <- 1
for (i in c(0,1)){
for (j in c(0,1)){
for (k in c(0,1)){
for (l in c(0,1)){
for (m in c(0,1)){
for (n in c(0,1)){
combos_list$toolcombo[p] <- paste(i,j,k,l,m,n)
combos_list$toolcombo2[p] <- paste(if(i){"tv"}else{"0"},if(j){"DVF"}else{"0"},
if(k){"tnv"}else{"0"},if(l){"VB"}else{"0"},
if(m){"VS"}else{"0"},if(n){"VS2"}else{"0"})
combos_list$tune_not_viral[p] <- i
combos_list$DVF[p] <- j
combos_list$tune_viral[p] <- k
combos_list$VIBRANT[p] <- l
combos_list$VS[p] <- m
combos_list$VS2[p] <- n
p <- p+1
}
}
}
}
}
}
combos_list <- combos_list[-1,]
this function builds a list of all of the combinations that the user wants to test. In this case, we’re comparing the performance of all unique combinations of the six tools.
build_score_list <- function(input_seqs, combos) {
There were 50 or more warnings (use warnings() to see the first 50)
output <- data.frame(precision=rep(0, nrow(combos)),
recall=rep(0, nrow(combos)),
F1=rep(0, nrow(combos)),
MCC=rep(0, nrow(combos)),
AUC=rep(0, nrow(combos)))
for (i in 1:nrow(combos)) {
keep_score <- getting_viral_set_1(input_seqs, include_vibrant = combos$VIBRANT[i],
include_virsorter = combos$VS[i],
include_virsorter2 = combos$VS2[i],
include_tuning_viral = combos$tune_viral[i],
include_tuning_not_viral = combos$tune_not_viral[i],
include_deepvirfinder = combos$DVF[i])
output[i,1:5] <- assess_performance(input_seqs$seqtype, keep_score)
output$toolcombo[i] <- paste(combos$tune_viral[i],combos$DVF[i],
combos$tune_not_viral[i], combos$VIBRANT[i],
combos$VS[i], combos$VS2[i])
}
output[is.na(output)] <- 0
return (output)
}
Calculate the performance of each pipeline
accuracy_scores <- data.frame(testing_set_index=rep(0, nrow(combos_list)*10),
precision=rep(0, nrow(combos_list)*10),
recall=rep(0, nrow(combos_list)*10),
F1=rep(0, nrow(combos_list)*10),
MCC=rep(0, nrow(combos_list)*10),
AUC=rep(0, nrow(combos_list)*10))
accuracy_scores <- cbind(testing_set_index=rep(1, nrow(combos_list)),
build_score_list(viruses[viruses$Index==1,], combos_list))
for (i in 2:10) {
accuracy_scores <- rbind(accuracy_scores,
cbind(testing_set_index=rep(i, nrow(combos_list)),
build_score_list(viruses[viruses$Index==i,], combos_list)))
}
accuracy_scores$numrules <- str_count(accuracy_scores$toolcombo, "1")
There were 27 warnings (use warnings() to see them)
#accuracy_scores <- accuracy_scores[order(accuracy_scores$numrules, decreasing=F),]
accuracy_scores <- accuracy_scores[order(accuracy_scores$MCC, decreasing=F),]
accuracy_scores$toolcombo <- factor(accuracy_scores$toolcombo, levels = unique(accuracy_scores$toolcombo))
accounting for rules that have multiple tools in them
accuracy_scores$numtools <- accuracy_scores$numrules
There were 27 warnings (use warnings() to see them)
for (i in 1:nrow(accuracy_scores)) {
toolcombo <- as.character(accuracy_scores$toolcombo[i])
if (substr(toolcombo, 1, 1)=="1") {
accuracy_scores$numtools[i] <- accuracy_scores$numtools[i] + 3
}
if (substr(toolcombo, 5, 5)=="1") {
accuracy_scores$numtools[i] <- accuracy_scores$numtools[i] + 4
}
}
accuracy_scores$numtools[accuracy_scores$numtools>6] <- 6
accuracy_scores$numrules <- as.factor(accuracy_scores$numrules)
accuracy_scores$numtools <- as.factor(accuracy_scores$numtools)
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
There were 27 warnings (use warnings() to see them)
Visualize how the precision, recall, and F1 scores change across pipelines.
p2 <- ggplot(accuracy_scores, aes(x=toolcombo, y=F1,
There were 50 or more warnings (use warnings() to see the first 50)
color=numrules, fill=numrules)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
ylab("F1 Score") +
scale_fill_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 1))
p2

ggplot(accuracy_scores, aes(x=toolcombo, y=precision,
color=numrules, fill=numrules)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
ylab("Precision") +
scale_fill_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores, aes(x=toolcombo, y=recall,
color=numrules, fill=numrules)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
ylab("Recall") +
scale_fill_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores, aes(x=precision, y=recall,
color=numrules, fill=numrules)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=20),
) +
xlab("Precision") +
ylab("Recall") +
scale_fill_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Rules",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores, aes(x=precision, y=recall,
color=numtools, fill=numtools)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=20),
) +
xlab("Precision") +
ylab("Recall") +
scale_fill_manual(name="Number of Tools",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Tools",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores, aes(x=toolcombo, y=MCC,
color=numrules, fill=numrules)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=20),
) +
xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
ylab("MCC") +
scale_fill_manual(name="Number of Rule Sets",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Rule Sets",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores, aes(x=toolcombo, y=MCC,
color=numtools, fill=numtools)) +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=20),
) +
xlab("Tool Combination (tv, DVF, tnv, VB, VS, VS2)") +
ylab("MCC") +
scale_fill_manual(name="Number of Tools",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="Number of Tools",
values = alpha(rev(pal(6)), 1))

accuracy_scores_melt <- accuracy_scores %>%
There were 50 or more warnings (use warnings() to see the first 50)
select(testing_set_index, precision, recall, MCC, numrules, numtools, toolcombo) %>%
pivot_longer(cols=c(precision, recall, MCC),
names_to="performance_metric",
values_to="performance_metric_score")
ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric=="MCC",], aes(x=numrules, y=performance_metric_score,
There were 50 or more warnings (use warnings() to see the first 50)
color=numrules, fill=numrules)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("MCC") +
xlab("Number of Rule Sets") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric=="MCC",], aes(x=numrules, y=performance_metric_score,
color=numtools, fill=numtools)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("MCC") +
xlab("Number of Rule Sets") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1))

ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric=="recall",], aes(x=numrules, y=performance_metric_score,
There were 30 warnings (use warnings() to see them)
color=numrules, fill=numrules)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("recall") +
xlab("Number of Rule Sets") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1))

NA
ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric!="MCC",], aes(x=numrules, y=performance_metric_score,
color=numrules, fill=numrules)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("Score") +
xlab("Number of Rule Sets") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
facet_wrap(~performance_metric)

NA
comparing metric with and without tuning rules
accuracy_scores_melt$tuning_inc <- "no"
There were 50 or more warnings (use warnings() to see the first 50)
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 1, 1)==1] <- "tv"
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 3, 3)==1] <- "tnv"
accuracy_scores_melt$tuning_inc[substring(accuracy_scores_melt$toolcombo, 1, 1)==1 &
substring(accuracy_scores_melt$toolcombo, 3, 3)==1] <- "both"
ggplot(accuracy_scores_melt, aes(x=tuning_inc, y=performance_metric_score)) +
geom_boxplot() +
geom_point(aes(color=numrules, fill=numrules), alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("MCC") +
xlab("Number of Tools") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
facet_wrap(~performance_metric)

ggplot(accuracy_scores_melt, aes(x=tuning_inc, y=performance_metric_score,
color=numrules, fill=numrules)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("MCC") +
xlab("Number of Tools") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
facet_wrap(~performance_metric)

ggplot(accuracy_scores_melt[accuracy_scores_melt$performance_metric!="MCC",], aes(x=tuning_inc, y=performance_metric_score)) +
geom_boxplot() +
geom_boxplot(aes(color=numrules, fill=numrules)) +
# geom_point(aes(color=numrules, fill=numrules), alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("Score") +
xlab("") +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.3)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 0.7)) +
facet_wrap(~performance_metric)

NA
write_tsv(accuracy_scores, "20221029_accuracy_scores.tsv")
There were 50 or more warnings (use warnings() to see the first 50)
to do: add in clustering and ordination like in the drinking water R notebook
Experimenting
high precision example
viruses$keep_score_high_precision <- getting_viral_set_1(viruses, include_deepvirfinder = T,
There were 20 warnings (use warnings() to see them)
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = T,
include_virsorter = F)
viruses$confusion_matrix_high_precision <- "true negative"
There were 25 warnings (use warnings() to see them)
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision<1] <- "false negative"
viruses$confusion_matrix_high_precision[viruses$seqtype=="virus" & viruses$keep_score_high_precision>=1] <- "true positive"
viruses$confusion_matrix_high_precision[viruses$seqtype!="virus" & viruses$keep_score_high_precision>=1] <- "false positive"
visualizing confusion matrix by taxa
confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype, viruses$Index))
The melt generic in data.table has been passed a table and will attempt to redirect to the relevant reshape2 method; please note that reshape2 is deprecated, and this redirection is now deprecated as well. To continue using melt methods from reshape2 while both libraries are attached, e.g. melt.list, you can prepend the namespace like reshape2::melt(table(viruses$confusion_matrix_high_precision, viruses$seqtype, viruses$Index)). In the next version, this warning will become an error.
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
length(grep("true", viruses$confusion_matrix_high_precision))/nrow(viruses)
[1] 0.9206364
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
There were 44 warnings (use warnings() to see them)
ggplot(confusion_by_taxa, aes(y=count, x=confusion_matrix,
There were 44 warnings (use warnings() to see them)
fill=confusion_matrix,
color=confusion_matrix)) +
geom_boxplot() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free")

this rule set had the highest precision, but as you can see, this comes with a big sacrifice in recall
high MCC example
viruses$keep_score_high_MCC <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 50 or more warnings (use warnings() to see the first 50)
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = T,
include_virsorter = T)
viruses$confusion_matrix_high_MCC <- "true negative"
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC<1] <- "false negative"
viruses$confusion_matrix_high_MCC[viruses$seqtype=="virus" & viruses$keep_score_high_MCC>=1] <- "true positive"
viruses$confusion_matrix_high_MCC[viruses$seqtype!="virus" & viruses$keep_score_high_MCC>=1] <- "false positive"
accuracy:
length(grep("true", viruses$confusion_matrix_high_MCC))/nrow(viruses)
[1] 0.935778
recall
length(grep("true positive", viruses$confusion_matrix_high_MCC))/length(grep("virus", viruses$seqtype))
[1] 0.7078
There were 24 warnings (use warnings() to see them)
TP <- table(viruses$confusion_matrix_high_MCC)[4]
FP <- table(viruses$confusion_matrix_high_MCC)[2]
TN <- table(viruses$confusion_matrix_high_MCC)[3]
FN <- table(viruses$confusion_matrix_high_MCC)[1]
precision <- as.numeric(TP/(TP+FP))
precision
[1] 0.6845261
recall <- as.numeric(TP/(TP+FN))
recall
[1] 0.7078
F1 <- as.numeric(2*precision*recall/(precision+recall))
F1
[1] 0.6959685
MCC <- as.numeric((TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN)))
MCC
[1] 0.6601921
precision=69%, recall=87%, MCC=77%
precision adjusting size to be equal viral/not viral
TP <- table(viruses$confusion_matrix_high_MCC)[4]
There were 48 warnings (use warnings() to see them)
FP <- table(viruses$confusion_matrix_high_MCC)[2]*.11
TN <- table(viruses$confusion_matrix_high_MCC)[3]*.11
FN <- table(viruses$confusion_matrix_high_MCC)[1]
precision <- as.numeric(TP/(TP+FP))
precision
[1] 0.9561163
recall <- as.numeric(TP/(TP+FN))
recall
[1] 0.7523
F1 <- as.numeric(2*precision*recall/(precision+recall))
F1
[1] 0.8420504
MCC <- as.numeric((TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN)))
MCC
[1] 0.7293445
precision=0.95, recall=0.87, F1=0.91, MCC=0.82
visualizing confusion matrix by taxa
confusion_by_taxa <- viruses %>% count(confusion_matrix_high_MCC, seqtype, Index)
There were 24 warnings (use warnings() to see them)
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(index),
There were 24 warnings (use warnings() to see them)
fill=confusion_matrix,
color=confusion_matrix)) +
geom_bar(stat="identity") +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()

differences based on genome size
viruses$size_class <- "3-5kb"
viruses$size_class[viruses$checkv_length>5000] <- "5-10kb"
viruses$size_class[viruses$checkv_length>10000] <- ">10kb"
confusion_by_taxa <- viruses %>% count(confusion_matrix_high_MCC, seqtype, size_class, Index)
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","size", "index", "count")
confusion_vir_called <- confusion_by_taxa %>% filter(confusion_matrix=="true positive" | confusion_matrix=="false positive")
There were 20 warnings (use warnings() to see them)
type_count <- viruses %>% count(seqtype, size_class, Index)
confusion_vir_called$per_viral <- 0
for (i in c(1:nrow(confusion_vir_called))) {
confusion_vir_called$per_viral[i] <- confusion_vir_called$count[i]/type_count$n[type_count$seqtype==confusion_vir_called$seqtype[i] &
type_count$Index==confusion_vir_called$index[i] &
type_count$size_class==confusion_vir_called$size[i]]*100
}
confusion_vir_called <- confusion_vir_called %>% group_by(seqtype, size) %>%
summarise(mean=mean(per_viral),
sd=sd(per_viral))
`summarise()` has grouped output by 'seqtype'. You can override using the `.groups` argument.
confusion_vir_called$size <- factor(confusion_vir_called$size,
levels = c("3-5kb", "5-10kb", ">10kb"))
ggplot(confusion_vir_called, aes(y=mean, x=size,
fill=seqtype,
color=seqtype)) +
geom_bar(stat="identity", position=position_dodge()) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,
position=position_dodge(.9)) +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
xlab("Length") +
ylab("Sequences Called Viral (%)")

viruses$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
There were 23 warnings (use warnings() to see them)
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses$keep_score_vb_dvf <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses$keep_score_vb_dvf_vs2 <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses$keep_score_vb_dvf_vs2_vs <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = T)
viruses$keep_score_vb_dvf_vs2_vs_tv <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = F,
include_virsorter = T)
viruses$keep_score_vb_dvf_vs2_vs_tv_tnv <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = T,
include_virsorter = T)
viruses$true_virus <- "not"
viruses$true_virus[viruses$seqtype=="virus"] <- "virus"
viruses_long_scores <- viruses %>%
select(contains("keep_score_vb"), size_class, true_virus) %>%
pivot_longer(cols=contains("keep_score_"),
names_to="rule_combination",
values_to="viral_score") %>%
mutate(viral_score=as.factor(round(viral_score))) %>%
group_by(rule_combination, viral_score, size_class, true_virus) %>%
summarise(n = n())
`summarise()` has grouped output by 'rule_combination', 'viral_score', 'size_class'. You can override using the `.groups` argument.
viruses_long_scores$size_class <- factor(viruses_long_scores$size_class,
levels = c("3-5kb", "5-10kb", ">10kb"))
viruses_long_scores_addition <- viruses_long_scores[(viruses_long_scores$true_virus=="virus" & (viruses_long_scores$rule_combination!="keep_score_vb_dvf_vs2_vs_tv_tnv") & viruses_long_scores$viral_score!="0"),]
There were 23 warnings (use warnings() to see them)
ggplot(viruses_long_scores_addition, aes(y=n, x=rule_combination,
fill=viral_score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_brewer(palette = "Purples") +
xlab("") +
ylab("Number of Sequences") +
scale_x_discrete(labels=c("VB", "VB+DVF", "VB+DVF+VS2", "VB+DVF+VS2+VS",
"VB+DVF+VS2+VS+addition")) +
facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 50 or more warnings (use warnings() to see the first 50)
fill=viral_score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_brewer(palette = "PuOr", ) +
xlab("") +
ylab("Number of Sequences") +
scale_x_discrete(labels=c("VB", "VB+DVF", "VB+DVF+VS2", "VB+DVF+VS2+VS",
"VB+DVF+VS2+VS+addition", "VB+DVF+VS2+VS+addition-removal")) +
facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
fill=viral_score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_brewer(palette = "PuOr", ) +
xlab("") +
ylab("Number of Sequences") +
facet_grid(size_class~true_virus, scales = "free")

Considering how each method contributes to the final prediction
viruses_high <- viruses[viruses$keep_score_vb_dvf_vs2_vs_tv>=1,]
There were 50 or more warnings (use warnings() to see the first 50)
viruses_high_mod <- viruses_high %>% select(keep_score_vb,keep_score_vb_dvf,
keep_score_vb_dvf_vs2, keep_score_vb_dvf_vs2_vs,
keep_score_vb_dvf_vs2_vs_tv, keep_score_vb_dvf_vs2_vs_tv_tnv)
#viruses_high_mod <- apply(viruses_high_mod, c(1,2), function(x) {if (x >= 1) {x <- 1} else {x <- 0}})
viruses_high_mod <- as_tibble(viruses_high_mod)
sm_m <- reshape2::melt(viruses_high_mod)
No id variables; using all as measure variables
colnames(sm_m) <- c("method", "viral_score")
sm_m <- sm_m[sm_m$viral_score>0,]
sm_m$score <- sm_m$viral_score
sm_m$score[sm_m$viral_score==0.5] <- "0.5"
sm_m$score[sm_m$viral_score>=1] <- "1"
sm_m$score[sm_m$viral_score>=2] <- "2"
sm_m$score[sm_m$viral_score>=3] <- "3"
sm_m$score[sm_m$viral_score>=4] <- "4"
sm_m$score[sm_m$viral_score>=5] <- "5"
sm_m$score <- factor(sm_m$score,
levels=c("0.5", "1", "2","3","4","5"))
ggplot(sm_m, aes(x=method, y=score,
fill=score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_manual(name = 'Viral Score',
values = alpha(c(viridis(6)), 1)) +
xlab("") +
ylab("Number of Sequences") +
coord_flip()
Coordinate system already present. Adding new coordinate system, which will replace the existing one.

Another way of visualizing between rule sets
viruses_mcc_alluvial <- data.frame(seqtype=viruses$seqtype,
keep_score_high_MCC=viruses$keep_score_high_MCC,
confusion_matrix_high_MCC=viruses$confusion_matrix_high_MCC)
viruses_mcc_alluvial$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses_mcc_alluvial$keep_score_dvf <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses_mcc_alluvial$keep_score_vs <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = F,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = T)
viruses_mcc_alluvial$keep_score_vs2 <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = F,
include_virsorter2 = T,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses_mcc_alluvial$keep_score_tv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = F,
include_virsorter2 = F,
include_tuning_viral = T,
include_tuning_not_viral = F,
include_virsorter = F)
viruses_mcc_alluvial$keep_score_tnv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = T,
include_virsorter = F)
viruses_mcc_alluvial %>%
count(seqtype, keep_score_high_MCC) %>%
spread(key = keep_score_high_MCC, value=n)
viruses_mcc_alluvial <- viruses_mcc_alluvial %>%
count(seqtype, keep_score_dvf, keep_score_vb, keep_score_vs,
keep_score_vs2, keep_score_tv, keep_score_tnv, keep_score_high_MCC) %>%
mutate(high_mcc_viral_score=factor(round(keep_score_high_MCC)))
ggplot(viruses_mcc_alluvial,
aes(axis1 = keep_score_dvf, axis2 = keep_score_vb,
axis3 = keep_score_vs, axis4 = keep_score_vs2,
axis5 = keep_score_tv, axis6 = keep_score_tnv,
y=n)) +
geom_alluvium(aes(fill=high_mcc_viral_score),
width = 0, knot.pos = 0, reverse = FALSE) +
geom_stratum(width = 1/5) +
theme_bw() +
geom_text(stat = "stratum", aes(label = after_stat(stratum)),
reverse = FALSE) +
theme(
axis.text.x=element_text(size=14, angle = 90)
) +
scale_x_continuous(breaks=c(1,2,3,4,5,6),
labels=c("dvf", "kj", "vs", "vs2",
"tv", "tnv")) +
scale_fill_brewer(palette = "PuOr", ) +
facet_wrap(~seqtype, scales="free_y")
clustering
viral_scores <- matrix(data=0, nrow=nrow(viruses), ncol=nrow(combos_list))
num_viruses <- data.frame(toolcombo=rep(0, nrow(combos_list)),
num_viruses=rep(0, nrow(combos_list)))
for (i in 1:nrow(combos_list)) {
viral_scores[,i] <- getting_viral_set_1(viruses, include_vibrant = combos_list$VIBRANT[i],
include_virsorter = combos_list$VS[i],
include_virsorter2 = combos_list$VS2[i],
include_tuning_viral = combos_list$tune_viral[i],
include_tuning_not_viral = combos_list$tune_not_viral[i],
include_deepvirfinder = combos_list$DVF[i])
if (max(viral_scores[,i])<=0) {
num_viruses$num_viruses[i] <- 0
}
else {
num_viruses$num_viruses[i] <- table(viral_scores[,i]>=1)[[2]]
}
num_viruses$toolcombo[i] <- combos_list$toolcombo[i]
num_viruses$toolcombo2[i] <- combos_list$toolcombo2[i]
}
num_viruses$numrules <- str_count(num_viruses$toolcombo, "1")
num_viruses <- num_viruses[order(num_viruses$num_viruses, decreasing=F),]
num_viruses$toolcombo <- factor(num_viruses$toolcombo, levels = unique(num_viruses$toolcombo))
num_viruses$toolcombo2 <- factor(num_viruses$toolcombo2, levels = unique(num_viruses$toolcombo2))
num_viruses$numrules <- as.factor(num_viruses$numrules)
viral_scores_nozeros <- viral_scores[rowSums(viral_scores)>0,]
viral_scores_nozeros <- viral_scores_nozeros + 1
viral_scores_nozeros <- as.data.frame(viral_scores_nozeros)
colnames(viral_scores_nozeros) <- num_viruses$toolcombo
library(phyloseq)
tooldata <- num_viruses
rownames(tooldata) <- tooldata$toolcombo
physeq_pooled <- phyloseq(otu_table(viral_scores_nozeros, taxa_are_rows = T),
sample_data(tooldata))
ordination <- phyloseq::ordinate(physeq =physeq_pooled, method = "PCoA", distance = "bray")
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
shape="numrules", color="num_viruses") +
geom_point(size = 3) +
theme_bw() +
geom_label(label=tooldata$toolcombo)
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
shape="numrules", color="num_viruses") +
geom_point(size = 3) +
theme_bw()
bray_dist <- phyloseq::distance(physeq_pooled, method="bray")
clusters <- hclust(dist(bray_dist))
plot(clusters)
myclusters <- cutree(clusters, h=0.8)
#names(myclusters[myclusters==1])
#names(myclusters[myclusters==2])
#names(myclusters[myclusters==3])
#names(myclusters[myclusters==4])
#names(myclusters[myclusters==5])
myclusters_df <- tibble(combo=names(myclusters),
cluster_index=myclusters)
myclusters_df <- separate(myclusters_df, col=combo, into=c("tnv", "DVF",
"tv", "VB",
"VS", "VS2"),
sep=" ", remove = F)
tool_count <- as.data.frame(rbind(table(myclusters_df$tnv, myclusters_df$cluster_index)[2,],
table(myclusters_df$DVF, myclusters_df$cluster_index)[2,],
table(myclusters_df$tv, myclusters_df$cluster_index)[2,],
table(myclusters_df$VB, myclusters_df$cluster_index)[2,],
table(myclusters_df$VS, myclusters_df$cluster_index)[2,],
table(myclusters_df$VS2, myclusters_df$cluster_index)[2,])
)
tool_count <- data.frame(t(apply(tool_count, c(1), function(x) {x <- x/table(myclusters_df$cluster_index)})))
tool_count$method <- c("tnv", "DVF", "tv", "VB", "VS", "VS2")
tool_count <- melt(tool_count)
colnames(tool_count) <- c("tool", "cluster_index", "tool_count_norm")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(tool_count, aes(x=tool, y=tool_count_norm,
fill=tool,
color=tool)) +
geom_bar(stat="identity") +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "none",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
#legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
xlab("Tool") +
ylab("Proportion of Times in Cluster") +
facet_wrap(~cluster_index, nrow=1)
accuracy_scores_melt <- accuracy_scores %>%
select(precision, recall, MCC, numrules, toolcombo) %>%
group_by(numrules, toolcombo) %>%
summarise(precision=mean(precision),
recall=mean(recall),
MCC=mean(MCC)) %>%
pivot_longer(cols=c(precision, recall, MCC),
names_to="performance_metric",
values_to="performance_metric_score")
myclusters_df <- inner_join(accuracy_scores_melt, myclusters_df,
by=c("toolcombo"="combo"))
myclusters_df$cluster_index <- as.factor(myclusters_df$cluster_index)
ggplot(myclusters_df, aes(x=cluster_index, y=performance_metric_score,
color=cluster_index, fill=cluster_index)) +
geom_boxplot() +
geom_point(alpha=0.5) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
ylab("MCC") +
xlab("Cluster") +
scale_fill_manual(name="",
values = alpha(rev(pal(9)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(9)), 1)) +
facet_wrap(~performance_metric)
all 6 tools example
viruses$keep_score_all <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = T,
include_virsorter = T)
viruses$confusion_matrix_all <- "true negative"
viruses$confusion_matrix_all[viruses$seqtype=="virus" & viruses$keep_score_all<1] <- "false negative"
viruses$confusion_matrix_all[viruses$seqtype=="virus" & viruses$keep_score_all>=1] <- "true positive"
viruses$confusion_matrix_all[viruses$seqtype!="virus" & viruses$keep_score_all>=1] <- "false positive"
TP <- table(viruses$confusion_matrix_all)[4]
FP <- table(viruses$confusion_matrix_all)[2]
TN <- table(viruses$confusion_matrix_all)[3]
FN <- table(viruses$confusion_matrix_all)[1]
precision <- TP/(TP+FP)
precision
recall <- TP/(TP+FN)
recall
F1 <- 2*precision*recall/(precision+recall)
F1
MCC <- (TP*TN-FP*FN)/sqrt(as.numeric(TP+FP)*as.numeric(TP+FN)*as.numeric(TN+FP)*as.numeric(TN+FN))
MCC
precision=62%, recall=92%, MCC=73%
visualizing confusion matrix by taxa
confusion_by_taxa <- melt(table(viruses$confusion_matrix_all, viruses$seqtype, viruses$Index))
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
table(viruses$confusion_matrix_all)
length(grep("true", viruses$confusion_matrix_all))/nrow(viruses)
length(grep("true positive", viruses$confusion_matrix_all))/length(grep("virus", viruses$seqtype))
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
fill=confusion_matrix,
color=confusion_matrix)) +
geom_bar(stat="identity") +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
high recall example
viruses$keep_score_high_recall <- getting_viral_set_1(viruses, include_deepvirfinder = T,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = F,
include_virsorter = T)
viruses$confusion_matrix_high_recall <- "true negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall<1] <- "false negative"
viruses$confusion_matrix_high_recall[viruses$seqtype=="virus" & viruses$keep_score_high_recall>=1] <- "true positive"
viruses$confusion_matrix_high_recall[viruses$seqtype!="virus" & viruses$keep_score_high_recall>=1] <- "false positive"
visualizing confusion matrix by taxa
confusion_by_taxa <- melt(table(viruses$confusion_matrix_high_recall, viruses$seqtype, viruses$Index))
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","Index", "count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
p2 <- ggplot(confusion_by_taxa, aes(x=count, y=as.factor(Index),
fill=confusion_matrix,
color=confusion_matrix)) +
geom_bar(stat="identity") +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
p2
accuracy:
length(grep("true", viruses$confusion_matrix_high_recall))/nrow(viruses)
0.887
recall
length(grep("true positive", viruses$confusion_matrix_high_recall))/length(grep("virus", viruses$seqtype))
recover almost all of the viruses this way, but more protist contamination
0.960
confusion_by_taxa <- viruses %>% count(confusion_matrix_high_recall, seqtype, size_class)
colnames(confusion_by_taxa) <- c("confusion_matrix", "seqtype","size", "count")
Visualizing the different sets
confusion_by_taxa_method <- viruses %>%
select(contains("confusion_matrix"), seqtype, Index) %>%
pivot_longer(cols=contains("confusion_matrix"),
names_to="confusion_matrix_type",
values_to="confusion_matrix_value") %>%
count(seqtype, Index, confusion_matrix_type, confusion_matrix_value)
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(confusion_by_taxa_method, aes(y=n, x=confusion_matrix_type,
fill=confusion_matrix_value,
color=confusion_matrix_value)) +
geom_boxplot() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free")
confusion_by_taxa_method <- viruses %>%
select(contains("confusion_matrix"), seqtype, Index) %>%
pivot_longer(cols=contains("confusion_matrix"),
names_to="confusion_matrix_type",
values_to="confusion_matrix_value") %>%
count(seqtype, Index, confusion_matrix_type, confusion_matrix_value) %>%
filter(grepl("true", confusion_matrix_value)) %>%
mutate(confusion_matrix_type=sub("confusion_matrix_", "", confusion_matrix_type))
ggplot(confusion_by_taxa_method, aes(y=n, x=confusion_matrix_type,
fill=confusion_matrix_value,
color=confusion_matrix_value)) +
geom_boxplot() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=10, angle=90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4))[3:4], 0.5),
labels=c(
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4))[3:4], 1),
labels=c(
"true negative", "true positive")) +
xlab("Tool Set") +
ylab("Contig Count") +
facet_wrap(~seqtype, scales = "free")
another way of visualizing the different tool sets scores
viruses$true_virus <- "not"
There were 40 warnings (use warnings() to see them)
viruses$true_virus[viruses$seqtype=="virus"] <- "virus"
viruses_long_scores <- viruses %>%
select(contains("keep_score_high"), contains("keep_score_all"), size_class, true_virus) %>%
pivot_longer(cols=contains("keep_score_"),
names_to="rule_combination",
values_to="viral_score") %>%
mutate(viral_score=as.factor(round(viral_score))) %>%
group_by(rule_combination, viral_score, size_class, true_virus) %>%
summarise(n = n())
`summarise()` has grouped output by 'rule_combination', 'viral_score', 'size_class'. You can override using the `.groups` argument.
viruses_long_scores$size_class <- factor(viruses_long_scores$size_class,
levels = c("3-5kb", "5-10kb", ">10kb"))
ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
fill=viral_score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_brewer(palette = "PuOr", ) +
xlab("") +
ylab("Number of Sequences") +
facet_grid(~true_virus, scales = "free")

ggplot(viruses_long_scores, aes(y=n, x=rule_combination,
There were 20 warnings (use warnings() to see them)
fill=viral_score)) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
legend.position = "bottom"
) +
scale_fill_brewer(palette = "PuOr", ) +
xlab("") +
ylab("Number of Sequences") +
facet_grid(size_class~true_virus, scales = "free")

Extra Stuff #####################################################################
ggplot(viruses, aes(x=checkv_length, y=keep_score_high_MCC,
fill=confusion_matrix_high_MCC,
color=confusion_matrix_high_MCC)) +
geom_point(stat="identity", shape=21) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Sequence Length (bp)") +
ylab("Pipeline Viral Score") +
facet_wrap(~seqtype) +
scale_x_log10()
ggplot(viruses, aes(x=checkv_completeness, y=hallmark,
fill=confusion_matrix_high_recall,
color=confusion_matrix_high_recall)) +
geom_point(stat="identity", shape=21) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("CheckV Completeness") +
ylab("Number of Hallmark Genes") +
facet_wrap(~seqtype) +
scale_x_log10()
ggplot(viruses, aes(x=checkv_completeness, y=keep_score_high_MCC,
fill=confusion_matrix_high_recall,
color=confusion_matrix_high_recall)) +
geom_point(stat="identity", shape=21) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("CheckV Completeness") +
ylab("Pipeline Viral Score") +
facet_wrap(~seqtype) +
scale_x_log10()
ggplot(viruses, aes(x=confusion_matrix_high_recall, y=checkv_length,
fill=confusion_matrix_high_recall,
color=confusion_matrix_high_recall)) +
geom_boxplot() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Sequence Length (bp)") +
ylab("Pipeline Viral Score") +
scale_y_log10()
looking at false negatives
viruses_false_negs <- viruses[(viruses$seqtype=="virus" & viruses$keep_score_high_recall<1),]
looking at protists calling viral
viruses_false_pos_protists <- viruses[(viruses$seqtype=="protist" & viruses$keep_score_high_recall>=1),]
Considering how each method contributes to the final prediction (high MCC)
viruses$keep_score_vb <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = T,
include_virsorter2 = F,
include_tuning_viral = F,
include_tuning_not_viral = F,
include_virsorter = F)
viruses$keep_score_vb_tv <- getting_viral_set_1(viruses, include_deepvirfinder = F,
include_vibrant = T,
include_virsorter2 = T,
include_tuning_viral = T,
include_tuning_not_viral = F,
include_virsorter = F)
viruses_high <- viruses[viruses$keep_score_vb_tv>=1,] #uncomment this line if want to use all 6 tools
viruses_high_mod <- viruses_high %>% select(keep_score_vb,
keep_score_vb_tv)
#viruses_high_mod <- apply(viruses_high_mod, c(1,2), function(x) {if (x >= 1) {x <- 1} else {x <- 0}})
viruses_high_mod <- as_tibble(viruses_high_mod)
sm_m <- reshape2::melt(viruses_high_mod)
colnames(sm_m) <- c("method", "score")
ggplot(sm_m, aes(x=method, y=score,
fill=as.factor(score))) +
geom_bar(stat="identity") +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom"
) +
scale_fill_manual(name = 'Number of Methods',
values = alpha(c(viridis(14)), 1)) +
xlab("Primary Method") +
ylab("Count of Viral Contigs") +
coord_flip()
ROC
library(pROC)
viruses$truepositive <- rep(0, nrow(viruses))
viruses$truepositive[viruses$seqtype=="virus"] <- 1
rocobj <- roc(viruses$truepositive, viruses$keep_score)
rocobj_all <- roc(viruses$truepositive, viruses$keep_score_all)
auc <- round(auc(viruses$truepositive, viruses$keep_score),4)
auc_all <- round(auc(viruses$truepositive, viruses$keep_score_all),4)
#create ROC plot
ggroc(rocobj, colour = 'steelblue', size = 2) +
ggtitle(paste0('ROC Curve ', '(AUC = ', auc, ')')) +
coord_equal()
ggroc(rocobj_all, colour = 'green', size = 2) +
ggtitle(paste0('ROC Curve ', '(AUC = ', auc_all, ')'))
Sensitivity: The probability that the model predicts a positive outcome for an observation when indeed the outcome is positive. Specificity: The probability that the model predicts a negative outcome for an observation when indeed the outcome is negative.
Comparing behavior of all testing sets combined (clustering analyses)
viral_scores <- matrix(data=0, nrow=nrow(viruses), ncol=nrow(combos_list))
num_viruses <- data.frame(toolcombo=rep(0, nrow(combos_list)),
num_viruses=rep(0, nrow(combos_list)))
for (i in 1:nrow(combos_list)) {
viral_scores[,i] <- getting_viral_set_1(viruses, include_vibrant = combos_list$VIBRANT[i],
include_virsorter = combos_list$VS[i],
include_virsorter2 = combos_list$VS2[i],
include_tuning = combos_list$CheckV[i],
include_kaiju = combos_list$Kaiju[i],
include_deepvirfinder = combos_list$DVF[i])
num_viruses$num_viruses[i] <- table(viral_scores[,i]>=1)[[2]]
num_viruses$toolcombo[i] <- combos_list$toolcombo[i]
num_viruses$toolcombo2[i] <- combos_list$toolcombo2[i]
}
num_viruses$numrules <- str_count(num_viruses$toolcombo, "1")
num_viruses <- num_viruses[order(num_viruses$num_viruses, decreasing=F),]
num_viruses$toolcombo <- factor(num_viruses$toolcombo, levels = unique(num_viruses$toolcombo))
num_viruses$toolcombo2 <- factor(num_viruses$toolcombo2, levels = unique(num_viruses$toolcombo2))
num_viruses$numrules <- as.factor(num_viruses$numrules)
ggplot(num_viruses, aes(x=toolcombo, y=num_viruses,
color=numrules, fill=numrules)) +
geom_point() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
ylab("Num Viruses Predicted")
ggplot(num_viruses, aes(x=toolcombo2, y=num_viruses,
color=numrules, fill=numrules)) +
geom_point() +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Tool Combination (CV, DVF, KJ, VB, VS, VS2)") +
ylab("Num Viruses Predicted")
ggplot(num_viruses, aes(x=numrules, y=num_viruses)) +
geom_boxplot(aes(color=numrules)) +
geom_point(aes(color=numrules, fill=numrules)) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14, angle = 90),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Tools") +
ylab("Num Viruses Predicted")
viral_scores_nozeros <- viral_scores[rowSums(viral_scores)>0,]
viral_scores_nozeros <- viral_scores_nozeros + 1
viral_scores_nozeros <- as.data.frame(viral_scores_nozeros)
colnames(viral_scores_nozeros) <- num_viruses$toolcombo2
library(phyloseq)
tooldata <- num_viruses
rownames(tooldata) <- tooldata$toolcombo2
physeq_pooled <- phyloseq(otu_table(viral_scores_nozeros, taxa_are_rows = T),
sample_data(tooldata))
ordination <- phyloseq::ordinate(physeq =physeq_pooled, method = "PCoA", distance = "bray")
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
shape="numrules", color="num_viruses") +
geom_point(size = 3) +
theme_bw() +
geom_label(label=tooldata$toolcombo)
phyloseq::plot_ordination(physeq = physeq_pooled, ordination = ordination,
shape="numrules", color="num_viruses") +
geom_point(size = 3) +
theme_bw()
to do: try coloring above based on the F1 scores of the testing set on each combination
bray_dist <- phyloseq::distance(physeq_pooled, method="bray")
clusters <- hclust(dist(bray_dist))
plot(clusters)
myclusters <- cutree(clusters, h=1.1)
names(myclusters[myclusters==1])
names(myclusters[myclusters==2])
names(myclusters[myclusters==3])
names(myclusters[myclusters==4])
names(myclusters[myclusters==5])
myclusters_df <- tibble(combo=names(myclusters),
cluster_index=myclusters)
myclusters_df <- separate(myclusters_df, col=combo, into=c("CheckV", "DVF",
"Kaiju", "VIBRANT",
"VirSorter", "VirSorter2"),
sep=" ", remove = F)
tool_count <- as.data.frame(rbind(table(myclusters_df$CheckV, myclusters_df$cluster_index)[2,],
table(myclusters_df$DVF, myclusters_df$cluster_index)[2,],
table(myclusters_df$Kaiju, myclusters_df$cluster_index)[2,],
table(myclusters_df$VIBRANT, myclusters_df$cluster_index)[2,],
table(myclusters_df$VirSorter, myclusters_df$cluster_index)[2,],
table(myclusters_df$VirSorter2, myclusters_df$cluster_index)[2,])
)
tool_count$method <- c("CheckV", "DVF", "Kaiju", "VIBRANT", "VirSorter", "VirSorter2")
tool_count <- melt(tool_count)
colnames(tool_count) <- c("tool", "cluster_index", "tool_count")
pal <- ggthemes::tableau_color_pal(palette="Tableau 10", type="regular")
ggplot(tool_count, aes(x=cluster_index, y=tool_count,
fill=cluster_index,
color=cluster_index)) +
geom_bar(stat="identity") +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(6)), 0.5)) +
scale_color_manual(name="",
values = alpha(rev(pal(6)), 1)) +
xlab("Cluster") +
ylab("Number of Times in Cluster") +
facet_wrap(~tool, scales = "free")
ggplot(viruses, aes(x=checkv_viral_genes, y=confusion_matrix_high_precision,
fill=confusion_matrix_high_precision,
color=confusion_matrix_high_precision)) +
geom_boxplot(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Viral Sequences") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
ggplot(viruses, aes(x=percent_viral, y=confusion_matrix_high_precision,
fill=confusion_matrix_high_precision,
color=confusion_matrix_high_precision)) +
geom_boxplot(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Percent Genes Viral") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
ggplot(viruses, aes(x=hallmark, y=confusion_matrix_high_precision,
fill=confusion_matrix_high_precision,
color=confusion_matrix_high_precision)) +
geom_boxplot(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Hallmark Genes") +
ylab("") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
fill=confusion_matrix_high_precision,
color=confusion_matrix_high_precision)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
scale_fill_manual(name="",
values = alpha(rev(pal(4)), 0.5),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
scale_color_manual(name="",
values = alpha(rev(pal(4)), 1),
labels=c("false negative", "false positive",
"true negative", "true positive")) +
xlab("Number of Hallmark Genes") +
ylab("Number of Viral Genes") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
viruses_false_positive <- viruses[viruses$confusion_matrix_high_precision=="false positive",]
viruses_false_negative <- viruses[viruses$confusion_matrix_high_precision=="false negative",]
ggplot(viruses, aes(x=hallmark, y=checkv_viral_genes,
fill=checkv_length,
color=checkv_length,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Number of Viral Genes") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
ggplot(viruses_false_positive, aes(x=hallmark, y=checkv_length,
fill=checkv_viral_genes,
color=checkv_viral_genes,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~seqtype, scales = "free") +
coord_flip()
ggplot(viruses_false_positive[viruses_false_positive$seqtype=="bacteria"], aes(x=hallmark, y=checkv_length,
fill=checkv_viral_genes,
color=checkv_viral_genes,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~Kaiju_Viral, scales = "free") +
coord_flip()
ggplot(viruses_false_positive[viruses_false_positive$seqtype=="fungi"], aes(x=hallmark, y=checkv_length,
fill=keep_score_high_precision,
color=keep_score_high_precision,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~Kaiju_Viral, scales = "free") +
coord_flip()
ggplot(viruses_false_positive[viruses_false_positive$seqtype=="protist"], aes(x=hallmark, y=checkv_length,
fill=checkv_viral_genes,
color=checkv_viral_genes,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~Kaiju_Viral, scales = "free") +
coord_flip()
ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
fill=checkv_viral_genes,
color=checkv_viral_genes,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~Kaiju_Viral, scales = "free") +
coord_flip()
ggplot(viruses_false_negative, aes(x=hallmark, y=checkv_length,
fill=keep_score_high_precision,
color=keep_score_high_precision,
shape=checkv_provirus)) +
geom_point(alpha=0.3) +
theme_light() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16),
) +
xlab("Number of Hallmark Genes") +
ylab("Contig Length") +
facet_wrap(~Kaiju_Viral, scales = "free") +
coord_flip()
table(viruses$hallmark[viruses$confusion_matrix_high_precision=="false positive"]>0)
table(viruses$percent_host[viruses$confusion_matrix_high_precision=="false positive"]<50)
Visualizing confusion matrix by number of tools
confusion_by_keep_score <- viruses %>%
select(contains("keep_score"), seqtype, Index) %>%
pivot_longer(cols=contains("keep_score"),
names_to="pipeline_type",
values_to="pipeline_value") %>%
mutate(pipeline_type=sub("keep_score_", "", pipeline_type)) %>%
count(seqtype, Index, pipeline_type, pipeline_value)
confusion_by_keep_score$confusion_matrix <- "true negative"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype=="virus" & confusion_by_keep_score$pipeline_value<1] <- "false negative"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype=="virus" & confusion_by_keep_score$pipeline_value>=1] <- "true positive"
confusion_by_keep_score$confusion_matrix[confusion_by_keep_score$seqtype!="virus" & confusion_by_keep_score$pipeline_value>=1] <- "false positive"
confusion_by_keep_score$keep_score_visualize <- confusion_by_keep_score$pipeline_value
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value>1] <- "> 1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==1] <- "1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==0.5] <- "0.5"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==0] <- "0"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==-0.5] <- "-0.5"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value==-1] <- "-1"
confusion_by_keep_score$keep_score_visualize[confusion_by_keep_score$pipeline_value<=-1] <- "< -1"
confusion_by_keep_score$keep_score_visualize <- factor(confusion_by_keep_score$keep_score_visualize,
levels=c("< -1", "-1", "-0.5", "0", "0.5","1", "> 1"))
ggplot(confusion_by_keep_score, aes(x=confusion_matrix, y=n,
fill=keep_score_visualize, color=keep_score_visualize)) +
geom_boxplot() +
theme_light() +
coord_flip() +
theme(
panel.grid.major.y = element_blank(),
panel.border = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "bottom",
axis.text.y=element_text(size=14),
axis.text.x=element_text(size=14),
legend.text=element_text(size=12),
axis.title=element_text(size=16)
) +
scale_color_manual(name = 'Viral Score',
values = alpha(c(viridis(6)), 1)) +
scale_fill_manual(name = 'Viral Score',
values = alpha(c(viridis(6)), 0.5)) +
xlab("Index") +
ylab("Sequence Count") +
facet_wrap(~pipeline_type, scales = "free")
LS0tCnRpdGxlOiAiVmlyYWwgU2VxdWVuY2UgU29ydGluZyBUb29scyBFdmFsdWF0aW9uIgphdXRob3I6IEJyaWRnZXQgSGVnYXJ0eSwgSmFtZXMgUmlkZGVsbApkYXRlOiAwNy0yMi0yMDIyCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KVGhpcyBSbWFya2Rvd24gZmlsZSBhc3Nlc3NlcyB0aGUgb3V0cHV0IG9mIENoZWNrViwgRGVlcFZpckZpbmRlciwgS2FpanUsClZJQlJBTlQsIFZpclNvcnRlciwgYW5kIFZpclNvcnRlcjIgb24gbXVsdGlwbGUgdHJhaW5pbmcgc2V0cyBvZiBtaWNyb2JpYWwgRE5BLCAKcHJpbWFyaWx5IGZyb20gTkNCSS4gQ3JlYXRlZCBmcm9tIGZ1bmdhbCwgdmlyYWwsIGJhY3RlcmlhbCwgYXJjaGVhZWwsIHByb3Rpc3QsCmFuZCBwbGFzbWlkIEROQSBzZXF1ZW5jZXMKClBsZWFzZSByZWFjaCBvdXQgdG8gSmFtZXMgUmlkZGVsbCAocmlkZGVsbC4yNkBidWNrZXllbWFpbC5vc3UuZWR1KSBvcgpCcmlkZ2V0IEhlZ2FydHkgKGJlaDUzQGNhc2UuZWR1KSByZWdhcmRpbmcgYW55IGlzc3Vlcywgb3Igb3BlbiBhbiBpc3N1ZSBvbiBnaXRodWIuCgpgYGB7ciBzZXR1cC1saWJyYXJ5fQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGx5cikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkocFJPQykKbGlicmFyeSgic3RyaW5nciIpCmBgYAoKSW1wb3J0IHRoZSBmaWxlIHRoYXQgY29tYmluZXMgdGhlIHJlc3VsdHMgZnJvbSBlYWNoIG9mIHRoZSB0b29scyBmcm9tIHJ1bm5pbmcgImNvbWJpbmluZ190b29sX291dHB1dC5SbWQiOgpgYGB7cn0KdmlydXNlcyA8LSByZWFkX3RzdigiLi4vSW50ZXJtZWRpYXJ5RmlsZXMvdmlyYWxfdG9vbHNfY29tYmluZWQudHN2IikKYGBgCih0aGlzIGlzIHN0aWxsIHRoZSBvbGQgcnVsZXMpCgpUaGlzIHNlY3Rpb24gZGVmaW5lcyBhIHZpcmFsbmVzcyBzY29yZSAia2VlcF9zY29yZSIgYmFzZWQgb24gdGhlIHRvb2wgY2xhc3NpZmljYXRpb25zLiAKQSBmaW5hbCBrZWVwX3Njb3JlIGFib3ZlIDEgaW5kaWNhdGVzIHdlIHdpbGwga2VlcCB0aGF0IHNlcXVlbmNlIGFuZCBjYWxsIGl0IHZpcmFsLgoKVklCUkFOVAogICAgUXVhbGl0eSA9PSAiSGlnaCBRdWFsaXR5IERyYWZ0IjogKzEKICAgIFF1YWxpdHkgPT0gIk1lZGl1bSBRdWFsaXR5IERyYWZ0IjogKzEKICAgIFF1YWxpdHkgPT0gIkxvdyBRdWFsaXR5IERyYWZ0IiAmIHByb3ZpcnVzOiArMC41CgpWaXJzb3J0ZXIyCiAgICBWaXJhbCA+PSA1MDogKzAuNQogICAgVmlyYWwgPj0gMC45NTogKzAuNQogICAgUk5BID49IDAuOTogKzEKICAgIGxhdmlkYXZpcmlkYWUgPj0gMC45OiArMQogICAgTkNMRFYgPj0gMC45OiArMQoKVmlyc29ydGVyCiAgICBjYXRlZ29yeSA9PSAgMSw0OiArMQogICAgY2F0ZWdvcnkgPT0gMiw1OiArMC41CgpEZWVwVmlyRmluZGVyOgogICAgU2NvcmUgPj0gMC43OiArMC41CgpUdW5pbmcgLSBObyBWaXJhbCBTaWduYXR1cmU6CiAgICBLYWlqdV92aXJhbCA9ICJjZWxsdWxhciBvcmdhbmlzbXMiOiAtMC41CiAgICBJZiBob3N0X2dlbmVzID41MCBhbmQgTk9UIHByb3ZpcnVzOiAtMSAKICAgIElmIHZpcmFsX2dlbmVzID09IDAgYW5kIGhvc3RfZ2VuZXMgPj0gMTogLTEKICAgIElmIDMqdmlyYWxfZ2VuZXMgPD0gaG9zdF9nZW5lcyBhbmQgTk9UIHByb3ZpcnVzOiAtMQogICAgSWYgbGVuZ3RoID4gNTAsMDAwIGFuZCBoYWxsbWFyayA8PTE6IC0xCiAgICBJZiBsZW5ndGggPCA1MDAwIGFuZCBjaGVja3YgY29tcGxldGVuZXNzIDw9IDc1OiAtMC41CgpUdW5pbmcgLSBWaXJhbCBTaWduYXR1cmU6CiAgICBLYWlqdV92aXJhbCA9ICJWaXJ1c2VzIjogKzAuNQogICAgSWYgJXVua25vd24gPj0gNzUgYW5kIGxlbmd0aCA8IDUwMDAwOiArMC41CiAgICBJZiAldmlyYWwgPj0gNTA6ICswLjUKICAgIEhhbGxtYXJrID4gMjogKzAuNQogICAgCgpUaGlzIHNjcmlwdCBwcm9kdWNlcyB2aXN1YWxpemF0aW9ucyBvZiB0aGVzZSBjb21iaW5lZCB2aXJhbCBzY29yaW5ncyBhbmQKaW5jbHVkZXMgZWNvbG9naWNhbCBtZXRyaWNzIGxpa2UgYWxwaGEgZGl2ZXJzaXR5LgoKWW91IGNhbiBkZWNpZGUgd2hpY2ggY29tYmluYXRpb24gaXMgYXBwcm9wcmlhdGUgZm9yIHRoZW0gYW5kIG9ubHkgbmVlZCB1c2UgdGhlCnRvb2xzIGFwcHJvcHJpYXRlIGZvciB5b3VyIGRhdGEuCgojIFRyeWluZyBuZXcgcnVsZSBzZXQKdGhpcyBzZXQgaXMgYmFzZWQgb24gb3B0aW1pemluZyBmb3IgdGhlIApgYGB7ciBnZXR0aW5nX3ZpcmFsX3NldF8xfQpnZXR0aW5nX3ZpcmFsX3NldF8xIDwtIGZ1bmN0aW9uKGlucHV0X3NlcXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50PUZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjI9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbD1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcj1GQUxTRSkgewogIAogIGtlZXBfc2NvcmUgPC0gcmVwKDAsIG5yb3coaW5wdXRfc2VxcykpCgogIGlmIChpbmNsdWRlX3ZpYnJhbnQpIHsKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJoaWdoIHF1YWxpdHkgZHJhZnQiXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0iaGlnaCBxdWFsaXR5IGRyYWZ0Il0gKyAxCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0ibWVkaXVtIHF1YWxpdHkgZHJhZnQiXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0ibWVkaXVtIHF1YWxpdHkgZHJhZnQiXSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJsb3cgcXVhbGl0eSBkcmFmdCIgJiBpbnB1dF9zZXFzJHByb3ZpcnVzXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0ibG93IHF1YWxpdHkgZHJhZnQiICYgaW5wdXRfc2VxcyRwcm92aXJ1c10gKyAwLjUKICB9CiAgCiAgaWYgKGluY2x1ZGVfdmlyc29ydGVyMikgewogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJG1heF9zY29yZT49NTBdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRtYXhfc2NvcmU+PTUwXSArIDAuNQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJG1heF9zY29yZT49OTVdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRtYXhfc2NvcmU+PTk1XSArIDAuNSAgCiAgfQogIAogIGlmIChpbmNsdWRlX3ZpcnNvcnRlcikgewogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT0xXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTFdICsgMQogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT0yXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTJdICsgMC41CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2F0ZWdvcnk9PTNdIDwtIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09M10gKyAwLjUKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09NF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT00XSArIDEKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09NV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT01XSArIDAuNSAKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjYXRlZ29yeT09Nl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNhdGVnb3J5PT02XSArIDAuNSAKICB9CiAgCiAgaWYgKGluY2x1ZGVfZGVlcHZpcmZpbmRlcikgewogICAgIyByZW1vdmUgaWYgRFZGIGNhbGxzIGl0IG5vdCB2aXJhbAogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJHNjb3JlPD0wLjcgJiBpbnB1dF9zZXFzJHB2YWx1ZTw9MC4wNV0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHNjb3JlPD0wLjcgJiBpbnB1dF9zZXFzJHB2YWx1ZTw9MC4wNV0gLSAxCiAgICAjIGFkZCBpZiBEVkYgY2FsbHMgdmlyYWwKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRzY29yZT49MC45ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDIwMDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuOSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gKyAwLjUKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRzY29yZT49MC43ICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPDIwMDAwXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkc2NvcmU+PTAuNyAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDwyMDAwMF0gKyAwLjUKICB9CiAgCiAgaWYgKGluY2x1ZGVfdHVuaW5nX3ZpcmFsKSB7CiAgIyB0dW5pbmcgYWRkaXRpb24KICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRoYWxsbWFyaz4yXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkaGFsbG1hcms+Ml0gKyAxCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkS2FpanVfVmlyYWw9PSJWaXJ1c2VzIl0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJEthaWp1X1ZpcmFsPT0iVmlydXNlcyJdICsgMQogICAgI25vdGU6IGhhZCB0cmllZCBwdWxsaW5nIG91dCB0aGlzIHJ1bGUsIGJ1dCBiZXR0ZXIgcmVjYWxsIHdpdGhvdXQgc2FjcmlmaWNpbmcgcHJlY2lzaW9uIHdpdGggaXQgaW4KICAgICNub3RlOiBoYXZpbmcga2FpanUgcmVtb3ZhbCBydWxlIGRpZG4ndCBoZWxwCiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkcGVyY2VudF91bmtub3duPj03NSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDw1MDAwMF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHBlcmNlbnRfdW5rbm93bj49NzUgJiBpbnB1dF9zZXFzJGNoZWNrdl9sZW5ndGg8NTAwMDBdICsgMC41CiAgICBrZWVwX3Njb3JlW2lucHV0X3NlcXMkdmlyYWw+PTUwIHwgaW5wdXRfc2VxcyRwZXJjZW50X3ZpcmFsPj01MF0gPC0ga2VlcF9zY29yZVtpbnB1dF9zZXFzJHZpcmFsPj01MCB8IGlucHV0X3NlcXMkcGVyY2VudF92aXJhbD49NTBdICsgMC41IAogICAga2VlcF9zY29yZVsoaW5wdXRfc2VxcyRjaGVja3ZfY29tcGxldGVuZXNzPj03NSB8IGlucHV0X3NlcXMkdmlicmFudF9xdWFsaXR5PT0iY29tcGxldGUgY2lyY3VsYXIiKSAmIChpbnB1dF9zZXFzJHZpcmFsPj01MCB8IGlucHV0X3NlcXMkcGVyY2VudF92aXJhbD49NTApXSA8LSBrZWVwX3Njb3JlWyhpbnB1dF9zZXFzJGNoZWNrdl9jb21wbGV0ZW5lc3M+PTc1IHwgaW5wdXRfc2VxcyR2aWJyYW50X3F1YWxpdHk9PSJjb21wbGV0ZSBjaXJjdWxhciIpICYgKGlucHV0X3NlcXMkdmlyYWw+PTUwIHwgaW5wdXRfc2VxcyRwZXJjZW50X3ZpcmFsPj01MCldICsgMC41IAogIH0KICAKICBpZiAoaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsKSB7CiAgICAjIHR1bmluZyByZW1vdmFsIAogICAga2VlcF9zY29yZVtpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzPjUwICYgIWlucHV0X3NlcXMkcHJvdmlydXNdIDwtIDAKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfdmlyYWxfZ2VuZXM9PTAgJiBpbnB1dF9zZXFzJGNoZWNrdl9ob3N0X2dlbmVzPj0xXSA8LSBrZWVwX3Njb3JlW2lucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzPT0wICYgaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcz49MV0gLSAxCiAgICBrZWVwX3Njb3JlWygoaW5wdXRfc2VxcyRjaGVja3ZfdmlyYWxfZ2VuZXMqMykgPD0gaW5wdXRfc2VxcyRjaGVja3ZfaG9zdF9nZW5lcykgJiAhaW5wdXRfc2VxcyRwcm92aXJ1c10gPC0ga2VlcF9zY29yZVsoKGlucHV0X3NlcXMkY2hlY2t2X3ZpcmFsX2dlbmVzKjMpIDw9IGlucHV0X3NlcXMkY2hlY2t2X2hvc3RfZ2VuZXMpICYgIWlucHV0X3NlcXMkcHJvdmlydXNdIC0gMSAKICAgIGtlZXBfc2NvcmVbaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPjUwMDAwMCAmIGlucHV0X3NlcXMkaGFsbG1hcms8PTFdIDwtIDAKICAgIGtlZXBfc2NvcmVbKGlucHV0X3NlcXMkY2hlY2t2X2NvbXBsZXRlbmVzczw9NzUpICYgaW5wdXRfc2VxcyRjaGVja3ZfbGVuZ3RoPD01MDAwXSA8LSBrZWVwX3Njb3JlWyhpbnB1dF9zZXFzJGNoZWNrdl9jb21wbGV0ZW5lc3M8PTc1KSAmIGlucHV0X3NlcXMkY2hlY2t2X2xlbmd0aDw9NTAwMF0gLSAwLjUgCiAgfQogIAogIHJldHVybihrZWVwX3Njb3JlKQogIAp9CmBgYAoKIyMgVHJ5aW5nIHdpdGggYWxsIG9mIHRoZSBydWxlcwpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX2FsbF8yIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50PVRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjI9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbD1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcj1UUlVFKQoKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yIDwtICJ0cnVlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsXzJbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2FsbF8yPDFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9hbGxfMj49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGxfMlt2aXJ1c2VzJHNlcXR5cGUhPSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsXzI+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKCnZpcnVzZXMkYWNjdXJhdGVfYWxsXzIgPC0gImZhbHNlIgp2aXJ1c2VzJGFjY3VyYXRlX2FsbF8yW2dyZXAoInRydWUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsXzIpXSA8LSAidHJ1ZSIKYGBgCgphY2N1cmFjeToKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIiwgdmlydXNlcyRhY2N1cmF0ZV9hbGxfMikpL25yb3codmlydXNlcykKYGBgCgpwcmVjaXNpb24KYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIHBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yKSkvbGVuZ3RoKGdyZXAoInBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yKSkKYGBgCgpyZWNhbGwKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIHBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yKSkvKGxlbmd0aChncmVwKCJ0cnVlIHBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yKSkrbGVuZ3RoKGdyZXAoImZhbHNlIG5lZ2F0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbF8yKSkpCmBgYAoKdmlzdWFsaXppbmcgYWNjdXJhY3kgYnkgdGF4YQpgYGB7cn0KYWNjdXJhdGVfYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkYWNjdXJhdGVfYWxsXzIsIHZpcnVzZXMkc2VxdHlwZSwgdmlydXNlcyRJbmRleCkpCmNvbG5hbWVzKGFjY3VyYXRlX2J5X3RheGEpIDwtIGMoImFjY3VyYXRlIiwgInNlcXR5cGUiLCJJbmRleCIsICJjb3VudCIpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoYWNjdXJhdGVfYnlfdGF4YSwgYWVzKHk9Y291bnQsIHg9YWNjdXJhdGUsCiAgICAgICAgICAgICAgICAgICBmaWxsPWFjY3VyYXRlLAogICAgICAgICAgICAgICAgICAgY29sb3I9YWNjdXJhdGUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDIpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UiLCAidHJ1ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoMikpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UiLCAidHJ1ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgojIEFzc2Vzc2luZyBwZXJmb3JtYW5jZSBhZ2FpbnN0IHRoZSAidHJ1dGgiCm5vdGUgdGhhdCB0aGlzIGlzIG9ubHkgYXMgYWNjdXJhdGUgYXMgdGhlIGFubm90YXRpb25zIG9mIHRoZSBpbnB1dCBzZXF1ZW5jZXMKCnRoaXMgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgcHJlY2lzaW9uLCByZWNhbGwsIGFuZCBGMSBzY29yZSBmb3IgZWFjaCBwaXBlbGluZQpgYGB7cn0KYXNzZXNzX3BlcmZvcm1hbmNlIDwtIGZ1bmN0aW9uKHNlcXR5cGUsIGtlZXBfc2NvcmUpIHsKICAKICB0cnVlcG9zaXRpdmUgPC0gcmVwKCJub3QgdmlyYWwiLCBsZW5ndGgoc2VxdHlwZSkpCiAgdHJ1ZXBvc2l0aXZlW3NlcXR5cGU9PSJ2aXJ1cyJdIDwtICJ2aXJhbCIKICAKICAjbWFrZSBjb25mdXNpb24gbWF0cml4CiAgY29uZnVzaW9uX21hdHJpeCA8LSByZXAoInRydWUgbmVnYXRpdmUiLCBsZW5ndGgoa2VlcF9zY29yZSkpCiAgY29uZnVzaW9uX21hdHJpeFt0cnVlcG9zaXRpdmU9PSJ2aXJhbCIgJiBrZWVwX3Njb3JlPD0xXSA8LSAiZmFsc2UgbmVnYXRpdmUiCiAgY29uZnVzaW9uX21hdHJpeFt0cnVlcG9zaXRpdmU9PSJ2aXJhbCIgJiBrZWVwX3Njb3JlPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKICBjb25mdXNpb25fbWF0cml4W3RydWVwb3NpdGl2ZT09Im5vdCB2aXJhbCIgJiBrZWVwX3Njb3JlPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCiAgCiAgVFAgPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeClbNF0KICBGUCA8LSB0YWJsZShjb25mdXNpb25fbWF0cml4KVsyXQogIFROIDwtIHRhYmxlKGNvbmZ1c2lvbl9tYXRyaXgpWzNdCiAgRk4gPC0gdGFibGUoY29uZnVzaW9uX21hdHJpeClbMV0KICAKICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogIHJlY2FsbCA8LSBUUC8oVFArRk4pCiAgRjEgPC0gMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24rcmVjYWxsKQogIAogIE1DQyA8LSAoVFAqVE4tRlAqRk4pL3NxcnQoYXMubnVtZXJpYyhUUCtGUCkqYXMubnVtZXJpYyhUUCtGTikqYXMubnVtZXJpYyhUTitGUCkqYXMubnVtZXJpYyhUTitGTikpCiAgCiAgYXVjIDwtIHJvdW5kKGF1Yyh0cnVlcG9zaXRpdmUsIGtlZXBfc2NvcmUpLDQpCiAgCiAgcGVyZm9ybWFuY2UgPC0gYyhwcmVjaXNpb24sIHJlY2FsbCwgRjEsIE1DQywgYXVjKQogIG5hbWVzKHBlcmZvcm1hbmNlKSA8LSBjKCJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIiwgIk1DQyIsICJBVUMiKQogIAogIHJldHVybihwZXJmb3JtYW5jZSkKfQpgYGAKCmNvbWJpbmF0aW9uIG9mIHRvb2xzIGxpc3QKYGBge3J9CmNvbWJvc19saXN0IDwtIGRhdGEuZnJhbWUodG9vbGNvbWJvPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZV9ub3RfdmlyYWw9cmVwKDAsIDY0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICBEVkY9cmVwKDAsIDY0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lX3ZpcmFsPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVklCUkFOVD1yZXAoMCwgNjQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIFZTPXJlcCgwLCA2NCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgVlMyPXJlcCgwLCA2NCkpCnAgPC0gMQoKZm9yIChpIGluIGMoMCwxKSl7CiAgZm9yIChqIGluIGMoMCwxKSl7CiAgICBmb3IgKGsgaW4gYygwLDEpKXsKICAgICAgZm9yIChsIGluIGMoMCwxKSl7CiAgICAgICAgZm9yIChtIGluIGMoMCwxKSl7CiAgICAgICAgICBmb3IgKG4gaW4gYygwLDEpKXsKICAgICAgICAgICAgY29tYm9zX2xpc3QkdG9vbGNvbWJvW3BdIDwtIHBhc3RlKGksaixrLGwsbSxuKQogICAgICAgICAgICBjb21ib3NfbGlzdCR0b29sY29tYm8yW3BdIDwtIHBhc3RlKGlmKGkpeyJ0diJ9ZWxzZXsiMCJ9LGlmKGopeyJEVkYifWVsc2V7IjAifSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZihrKXsidG52In1lbHNleyIwIn0saWYobCl7IlZCIn1lbHNleyIwIn0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYobSl7IlZTIn1lbHNleyIwIn0saWYobil7IlZTMiJ9ZWxzZXsiMCJ9KQogICAgICAgICAgICBjb21ib3NfbGlzdCR0dW5lX25vdF92aXJhbFtwXSA8LSBpCiAgICAgICAgICAgIGNvbWJvc19saXN0JERWRltwXSA8LSBqCiAgICAgICAgICAgIGNvbWJvc19saXN0JHR1bmVfdmlyYWxbcF0gPC0gawogICAgICAgICAgICBjb21ib3NfbGlzdCRWSUJSQU5UW3BdIDwtIGwKICAgICAgICAgICAgY29tYm9zX2xpc3QkVlNbcF0gPC0gbQogICAgICAgICAgICBjb21ib3NfbGlzdCRWUzJbcF0gPC0gbgogICAgICAgICAgICBwIDwtIHArMQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KfQoKY29tYm9zX2xpc3QgPC0gY29tYm9zX2xpc3RbLTEsXQpgYGAKCnRoaXMgZnVuY3Rpb24gYnVpbGRzIGEgbGlzdCBvZiBhbGwgb2YgdGhlIGNvbWJpbmF0aW9ucyB0aGF0IHRoZSB1c2VyIHdhbnRzIHRvIAp0ZXN0LiAKSW4gdGhpcyBjYXNlLCB3ZSdyZSBjb21wYXJpbmcgdGhlIHBlcmZvcm1hbmNlIG9mIGFsbCB1bmlxdWUgY29tYmluYXRpb25zIG9mIHRoZSAKc2l4IHRvb2xzLgpgYGB7cn0KYnVpbGRfc2NvcmVfbGlzdCA8LSBmdW5jdGlvbihpbnB1dF9zZXFzLCBjb21ib3MpIHsKICBvdXRwdXQgPC0gZGF0YS5mcmFtZShwcmVjaXNpb249cmVwKDAsIG5yb3coY29tYm9zKSksCiAgICAgICAgICAgICAgICAgICAgICAgcmVjYWxsPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIEYxPXJlcCgwLCBucm93KGNvbWJvcykpLAogICAgICAgICAgICAgICAgICAgICAgIE1DQz1yZXAoMCwgbnJvdyhjb21ib3MpKSwKICAgICAgICAgICAgICAgICAgICAgICBBVUM9cmVwKDAsIG5yb3coY29tYm9zKSkpCiAgZm9yIChpIGluIDE6bnJvdyhjb21ib3MpKSB7CiAgICBrZWVwX3Njb3JlIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEoaW5wdXRfc2VxcywgaW5jbHVkZV92aWJyYW50ID0gY29tYm9zJFZJQlJBTlRbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBjb21ib3MkVlNbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gY29tYm9zJFZTMltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IGNvbWJvcyR0dW5lX3ZpcmFsW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IGNvbWJvcyR0dW5lX25vdF92aXJhbFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBjb21ib3MkRFZGW2ldKQogIAogICAgb3V0cHV0W2ksMTo1XSA8LSBhc3Nlc3NfcGVyZm9ybWFuY2UoaW5wdXRfc2VxcyRzZXF0eXBlLCBrZWVwX3Njb3JlKQogICAgCiAgICBvdXRwdXQkdG9vbGNvbWJvW2ldIDwtIHBhc3RlKGNvbWJvcyR0dW5lX3ZpcmFsW2ldLGNvbWJvcyREVkZbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJvcyR0dW5lX25vdF92aXJhbFtpXSwgY29tYm9zJFZJQlJBTlRbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJvcyRWU1tpXSwgY29tYm9zJFZTMltpXSkKICB9CiAgCiAgb3V0cHV0W2lzLm5hKG91dHB1dCldIDwtIDAKCiAgcmV0dXJuIChvdXRwdXQpCn0KYGBgCgojIyBDYWxjdWxhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIGVhY2ggcGlwZWxpbmUKYGBge3J9CmFjY3VyYWN5X3Njb3JlcyA8LSBkYXRhLmZyYW1lKHRlc3Rpbmdfc2V0X2luZGV4PXJlcCgwLCBucm93KGNvbWJvc19saXN0KSoxMCksCiAgICAgICAgICAgICAgICAgICAgICBwcmVjaXNpb249cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgICByZWNhbGw9cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKjEwKSwKICAgICAgICAgICAgICAgICAgICAgICBGMT1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLAogICAgICAgICAgICAgICAgICAgICAgIE1DQz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApLCAKICAgICAgICAgICAgICAgICAgICAgIEFVQz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkqMTApKQoKYWNjdXJhY3lfc2NvcmVzIDwtIGNiaW5kKHRlc3Rpbmdfc2V0X2luZGV4PXJlcCgxLCBucm93KGNvbWJvc19saXN0KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ1aWxkX3Njb3JlX2xpc3QodmlydXNlc1t2aXJ1c2VzJEluZGV4PT0xLF0sIGNvbWJvc19saXN0KSkKZm9yIChpIGluIDI6MTApIHsKICBhY2N1cmFjeV9zY29yZXMgPC0gcmJpbmQoYWNjdXJhY3lfc2NvcmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjYmluZCh0ZXN0aW5nX3NldF9pbmRleD1yZXAoaSwgbnJvdyhjb21ib3NfbGlzdCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidWlsZF9zY29yZV9saXN0KHZpcnVzZXNbdmlydXNlcyRJbmRleD09aSxdLCBjb21ib3NfbGlzdCkpKQp9CmBgYAoKCmBgYHtyfQphY2N1cmFjeV9zY29yZXMkbnVtcnVsZXMgPC0gc3RyX2NvdW50KGFjY3VyYWN5X3Njb3JlcyR0b29sY29tYm8sICIxIikKI2FjY3VyYWN5X3Njb3JlcyA8LSBhY2N1cmFjeV9zY29yZXNbb3JkZXIoYWNjdXJhY3lfc2NvcmVzJG51bXJ1bGVzLCBkZWNyZWFzaW5nPUYpLF0KYWNjdXJhY3lfc2NvcmVzIDwtIGFjY3VyYWN5X3Njb3Jlc1tvcmRlcihhY2N1cmFjeV9zY29yZXMkTUNDLCBkZWNyZWFzaW5nPUYpLF0KYWNjdXJhY3lfc2NvcmVzJHRvb2xjb21ibyA8LSBmYWN0b3IoYWNjdXJhY3lfc2NvcmVzJHRvb2xjb21ibywgbGV2ZWxzID0gdW5pcXVlKGFjY3VyYWN5X3Njb3JlcyR0b29sY29tYm8pKQpgYGAKCmFjY291bnRpbmcgZm9yIHJ1bGVzIHRoYXQgaGF2ZSBtdWx0aXBsZSB0b29scyBpbiB0aGVtCmBgYHtyfQphY2N1cmFjeV9zY29yZXMkbnVtdG9vbHMgPC0gYWNjdXJhY3lfc2NvcmVzJG51bXJ1bGVzCgpmb3IgKGkgaW4gMTpucm93KGFjY3VyYWN5X3Njb3JlcykpIHsKICB0b29sY29tYm8gPC0gYXMuY2hhcmFjdGVyKGFjY3VyYWN5X3Njb3JlcyR0b29sY29tYm9baV0pCiAgaWYgKHN1YnN0cih0b29sY29tYm8sIDEsIDEpPT0iMSIpIHsKICAgIGFjY3VyYWN5X3Njb3JlcyRudW10b29sc1tpXSA8LSBhY2N1cmFjeV9zY29yZXMkbnVtdG9vbHNbaV0gKyAzCiAgfQogIAogIGlmIChzdWJzdHIodG9vbGNvbWJvLCA1LCA1KT09IjEiKSB7CiAgICBhY2N1cmFjeV9zY29yZXMkbnVtdG9vbHNbaV0gPC0gYWNjdXJhY3lfc2NvcmVzJG51bXRvb2xzW2ldICsgNAogIH0KfQoKYWNjdXJhY3lfc2NvcmVzJG51bXRvb2xzW2FjY3VyYWN5X3Njb3JlcyRudW10b29scz42XSA8LSA2CmBgYAoKYGBge3J9CmFjY3VyYWN5X3Njb3JlcyRudW1ydWxlcyA8LSBhcy5mYWN0b3IoYWNjdXJhY3lfc2NvcmVzJG51bXJ1bGVzKQphY2N1cmFjeV9zY29yZXMkbnVtdG9vbHMgPC0gYXMuZmFjdG9yKGFjY3VyYWN5X3Njb3JlcyRudW10b29scykKYGBgCgoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCgojIyBWaXN1YWxpemUgaG93IHRoZSBwcmVjaXNpb24sIHJlY2FsbCwgYW5kIEYxIHNjb3JlcyBjaGFuZ2UgYWNyb3NzIHBpcGVsaW5lcy4KYGBge3J9CnAyIDwtIGdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT1GMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uICh0diwgRFZGLCB0bnYsIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJGMSBTY29yZSIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpCnAyCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9cHJlY2lzaW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlByZWNpc2lvbiIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9cmVjYWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIlJlY2FsbCIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZXMiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD1wcmVjaXNpb24sIHk9cmVjYWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MjApLAogICkgKwogIHhsYWIoIlByZWNpc2lvbiIpICsKICB5bGFiKCJSZWNhbGwiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iTnVtYmVyIG9mIFJ1bGVzIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iTnVtYmVyIG9mIFJ1bGVzIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKQoKZ2dwbG90KGFjY3VyYWN5X3Njb3JlcywgYWVzKHg9cHJlY2lzaW9uLCB5PXJlY2FsbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW10b29scywgZmlsbD1udW10b29scykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTIwKSwKICApICsKICB4bGFiKCJQcmVjaXNpb24iKSArCiAgeWxhYigiUmVjYWxsIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9Ik51bWJlciBvZiBUb29scyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9Ik51bWJlciBvZiBUb29scyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkKCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzLCBhZXMoeD10b29sY29tYm8sIHk9TUNDLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MjApLAogICkgKwogIHhsYWIoIlRvb2wgQ29tYmluYXRpb24gKHR2LCBEVkYsIHRudiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIk1DQyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJOdW1iZXIgb2YgUnVsZSBTZXRzIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iTnVtYmVyIG9mIFJ1bGUgU2V0cyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkKCmdncGxvdChhY2N1cmFjeV9zY29yZXMsIGFlcyh4PXRvb2xjb21ibywgeT1NQ0MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtdG9vbHMsIGZpbGw9bnVtdG9vbHMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0yMCksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAodHYsIERWRiwgdG52LCBWQiwgVlMsIFZTMikiKSArCiAgeWxhYigiTUNDIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9Ik51bWJlciBvZiBUb29scyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9Ik51bWJlciBvZiBUb29scyIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkKYGBgCgpgYGB7cn0KYWNjdXJhY3lfc2NvcmVzX21lbHQgPC0gYWNjdXJhY3lfc2NvcmVzICU+JSAKICBzZWxlY3QodGVzdGluZ19zZXRfaW5kZXgsIHByZWNpc2lvbiwgcmVjYWxsLCBNQ0MsIG51bXJ1bGVzLCBudW10b29scywgdG9vbGNvbWJvKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1jKHByZWNpc2lvbiwgcmVjYWxsLCBNQ0MpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89InBlcmZvcm1hbmNlX21ldHJpYyIsCiAgICAgICAgICAgICAgIHZhbHVlc190bz0icGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlIikKYGBgCgpgYGB7cn0KZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0W2FjY3VyYWN5X3Njb3Jlc19tZWx0JHBlcmZvcm1hbmNlX21ldHJpYz09Ik1DQyIsXSwgYWVzKHg9bnVtcnVsZXMsIHk9cGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXJ1bGVzLCBmaWxsPW51bXJ1bGVzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB5bGFiKCJNQ0MiKSArCiAgeGxhYigiTnVtYmVyIG9mIFJ1bGUgU2V0cyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpIAogIAoKZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0W2FjY3VyYWN5X3Njb3Jlc19tZWx0JHBlcmZvcm1hbmNlX21ldHJpYz09Ik1DQyIsXSwgYWVzKHg9bnVtcnVsZXMsIHk9cGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yPW51bXRvb2xzLCBmaWxsPW51bXRvb2xzKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBnZW9tX3BvaW50KGFscGhhPTAuNSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB5bGFiKCJNQ0MiKSArCiAgeGxhYigiTnVtYmVyIG9mIFJ1bGUgU2V0cyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpIApgYGAKCmBgYHtyfQpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzX21lbHRbYWNjdXJhY3lfc2NvcmVzX21lbHQkcGVyZm9ybWFuY2VfbWV0cmljPT0icmVjYWxsIixdLCBhZXMoeD1udW1ydWxlcywgeT1wZXJmb3JtYW5jZV9tZXRyaWNfc2NvcmUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoInJlY2FsbCIpICsKICB4bGFiKCJOdW1iZXIgb2YgUnVsZSBTZXRzIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkgCiAgCmBgYAoKYGBge3J9CmdncGxvdChhY2N1cmFjeV9zY29yZXNfbWVsdFthY2N1cmFjeV9zY29yZXNfbWVsdCRwZXJmb3JtYW5jZV9tZXRyaWMhPSJNQ0MiLF0sIGFlcyh4PW51bXJ1bGVzLCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeWxhYigiU2NvcmUiKSArCiAgeGxhYigiTnVtYmVyIG9mIFJ1bGUgU2V0cyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpICsKICBmYWNldF93cmFwKH5wZXJmb3JtYW5jZV9tZXRyaWMpCiAgCmBgYApjb21wYXJpbmcgbWV0cmljIHdpdGggYW5kIHdpdGhvdXQgdHVuaW5nIHJ1bGVzCmBgYHtyfQphY2N1cmFjeV9zY29yZXNfbWVsdCR0dW5pbmdfaW5jIDwtICJubyIKYWNjdXJhY3lfc2NvcmVzX21lbHQkdHVuaW5nX2luY1tzdWJzdHJpbmcoYWNjdXJhY3lfc2NvcmVzX21lbHQkdG9vbGNvbWJvLCAxLCAxKT09MV0gPC0gInR2IgphY2N1cmFjeV9zY29yZXNfbWVsdCR0dW5pbmdfaW5jW3N1YnN0cmluZyhhY2N1cmFjeV9zY29yZXNfbWVsdCR0b29sY29tYm8sIDMsIDMpPT0xXSA8LSAidG52IgphY2N1cmFjeV9zY29yZXNfbWVsdCR0dW5pbmdfaW5jW3N1YnN0cmluZyhhY2N1cmFjeV9zY29yZXNfbWVsdCR0b29sY29tYm8sIDEsIDEpPT0xICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnN0cmluZyhhY2N1cmFjeV9zY29yZXNfbWVsdCR0b29sY29tYm8sIDMsIDMpPT0xXSA8LSAiYm90aCIKYGBgCgpgYGB7cn0KZ2dwbG90KGFjY3VyYWN5X3Njb3Jlc19tZWx0LCBhZXMoeD10dW5pbmdfaW5jLCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpLCBhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeWxhYigiTUNDIikgKwogIHhsYWIoIk51bWJlciBvZiBUb29scyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpICsKICBmYWNldF93cmFwKH5wZXJmb3JtYW5jZV9tZXRyaWMpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzX21lbHQsIGFlcyh4PXR1bmluZ19pbmMsIHk9cGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeWxhYigiTUNDIikgKwogIHhsYWIoIk51bWJlciBvZiBUb29scyIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpICsKICBmYWNldF93cmFwKH5wZXJmb3JtYW5jZV9tZXRyaWMpCgpnZ3Bsb3QoYWNjdXJhY3lfc2NvcmVzX21lbHRbYWNjdXJhY3lfc2NvcmVzX21lbHQkcGVyZm9ybWFuY2VfbWV0cmljIT0iTUNDIixdLCBhZXMoeD10dW5pbmdfaW5jLCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICMgZ2VvbV9wb2ludChhZXMoY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpLCBhbHBoYT0wLjUpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeWxhYigiU2NvcmUiKSArCiAgeGxhYigiIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjcpKSArCiAgZmFjZXRfd3JhcCh+cGVyZm9ybWFuY2VfbWV0cmljKQogIApgYGAKCmBgYHtyfQp3cml0ZV90c3YoYWNjdXJhY3lfc2NvcmVzLCAiMjAyMjEwMjlfYWNjdXJhY3lfc2NvcmVzLnRzdiIpCmBgYAoKdG8gZG86IGFkZCBpbiBjbHVzdGVyaW5nIGFuZCBvcmRpbmF0aW9uIGxpa2UgaW4gdGhlIGRyaW5raW5nIHdhdGVyIFIgbm90ZWJvb2sKCiMgRXhwZXJpbWVudGluZwoKIyMgaGlnaCBwcmVjaXNpb24gZXhhbXBsZQpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiA8LSAidHJ1ZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbjwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvblt2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9wcmVjaXNpb24+PTFdIDwtICJ0cnVlIHBvc2l0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb25bdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwgdmlydXNlcyRzZXF0eXBlLCB2aXJ1c2VzJEluZGV4KSkKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsIkluZGV4IiwgImNvdW50IikKYGBgCgpgYGB7cn0KbGVuZ3RoKGdyZXAoInRydWUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKS9ucm93KHZpcnVzZXMpCmBgYAoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGEsIGFlcyh5PWNvdW50LCB4PWNvbmZ1c2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4KSkgKwogIGdlb21fYm94cGxvdCgpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKQpgYGAKdGhpcyBydWxlIHNldCBoYWQgdGhlIGhpZ2hlc3QgcHJlY2lzaW9uLCBidXQgYXMgeW91IGNhbiBzZWUsIHRoaXMgY29tZXMgd2l0aCBhIGJpZyBzYWNyaWZpY2UgaW4gcmVjYWxsCgoKCiMjIGhpZ2ggTUNDIGV4YW1wbGUKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQyA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IFQpCmBgYAoKCmBgYHtyfQp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MgPC0gInRydWUgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQ1t2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M8MV0gPC0gImZhbHNlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0NbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDW3ZpcnVzZXMkc2VxdHlwZSE9InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz49MV0gPC0gImZhbHNlIHBvc2l0aXZlIgpgYGAKCmFjY3VyYWN5OgpgYGB7cn0KbGVuZ3RoKGdyZXAoInRydWUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpKS9ucm93KHZpcnVzZXMpCmBgYAoKcmVjYWxsCmBgYHtyfQpsZW5ndGgoZ3JlcCgidHJ1ZSBwb3NpdGl2ZSIsIHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQykpL2xlbmd0aChncmVwKCJ2aXJ1cyIsIHZpcnVzZXMkc2VxdHlwZSkpCmBgYAoKYGBge3J9ClRQIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQylbNF0KRlAgPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVsyXQpUTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzNdCkZOIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX01DQylbMV0KCnByZWNpc2lvbiA8LSBhcy5udW1lcmljKFRQLyhUUCtGUCkpCnByZWNpc2lvbgpyZWNhbGwgPC0gYXMubnVtZXJpYyhUUC8oVFArRk4pKQpyZWNhbGwKRjEgPC0gYXMubnVtZXJpYygyKnByZWNpc2lvbipyZWNhbGwvKHByZWNpc2lvbityZWNhbGwpKQpGMQoKTUNDIDwtIGFzLm51bWVyaWMoKFRQKlROLUZQKkZOKS9zcXJ0KGFzLm51bWVyaWMoVFArRlApKmFzLm51bWVyaWMoVFArRk4pKmFzLm51bWVyaWMoVE4rRlApKmFzLm51bWVyaWMoVE4rRk4pKSkKTUNDCmBgYAoKcHJlY2lzaW9uPTY5JSwgcmVjYWxsPTg3JSwgTUNDPTc3JQoKcHJlY2lzaW9uIGFkanVzdGluZyBzaXplIHRvIGJlIGVxdWFsIHZpcmFsL25vdCB2aXJhbApgYGB7cn0KVFAgPC0gdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKVs0XQpGUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzJdKi4xMQpUTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzNdKi4xMQpGTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MpWzFdCgpwcmVjaXNpb24gPC0gYXMubnVtZXJpYyhUUC8oVFArRlApKQpwcmVjaXNpb24KcmVjYWxsIDwtIGFzLm51bWVyaWMoVFAvKFRQK0ZOKSkKcmVjYWxsCkYxIDwtIGFzLm51bWVyaWMoMipwcmVjaXNpb24qcmVjYWxsLyhwcmVjaXNpb24rcmVjYWxsKSkKRjEKCk1DQyA8LSBhcy5udW1lcmljKChUUCpUTi1GUCpGTikvc3FydChhcy5udW1lcmljKFRQK0ZQKSphcy5udW1lcmljKFRQK0ZOKSphcy5udW1lcmljKFROK0ZQKSphcy5udW1lcmljKFROK0ZOKSkpCk1DQwpgYGAKCnByZWNpc2lvbj0wLjk1LCByZWNhbGw9MC44NywgRjE9MC45MSwgTUNDPTAuODIKCgoKCgp2aXN1YWxpemluZyBjb25mdXNpb24gbWF0cml4IGJ5IHRheGEKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhIDwtIHZpcnVzZXMgJT4lIGNvdW50KGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MsIHNlcXR5cGUsIEluZGV4KQoKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsImluZGV4IiwgImNvdW50IikKYGBgCgoKYGBge3J9CnBhbCA8LSBnZ3RoZW1lczo6dGFibGVhdV9jb2xvcl9wYWwocGFsZXR0ZT0iVGFibGVhdSAxMCIsIHR5cGU9InJlZ3VsYXIiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY29uZnVzaW9uX2J5X3RheGEsIGFlcyh4PWNvdW50LCB5PWFzLmZhY3RvcihpbmRleCksCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4KSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBTZXF1ZW5jZXMiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgoKZGlmZmVyZW5jZXMgYmFzZWQgb24gZ2Vub21lIHNpemUKYGBge3J9CnZpcnVzZXMkc2l6ZV9jbGFzcyA8LSAiMy01a2IiCnZpcnVzZXMkc2l6ZV9jbGFzc1t2aXJ1c2VzJGNoZWNrdl9sZW5ndGg+NTAwMF0gPC0gIjUtMTBrYiIKdmlydXNlcyRzaXplX2NsYXNzW3ZpcnVzZXMkY2hlY2t2X2xlbmd0aD4xMDAwMF0gPC0gIj4xMGtiIgpgYGAKCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSB2aXJ1c2VzICU+JSBjb3VudChjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDLCBzZXF0eXBlLCBzaXplX2NsYXNzLCBJbmRleCkKCmNvbG5hbWVzKGNvbmZ1c2lvbl9ieV90YXhhKSA8LSBjKCJjb25mdXNpb25fbWF0cml4IiwgInNlcXR5cGUiLCJzaXplIiwgImluZGV4IiwgImNvdW50IikKYGBgCgpgYGB7cn0KY29uZnVzaW9uX3Zpcl9jYWxsZWQgPC0gY29uZnVzaW9uX2J5X3RheGEgJT4lIGZpbHRlcihjb25mdXNpb25fbWF0cml4PT0idHJ1ZSBwb3NpdGl2ZSIgfCBjb25mdXNpb25fbWF0cml4PT0iZmFsc2UgcG9zaXRpdmUiKSAKCnR5cGVfY291bnQgPC0gdmlydXNlcyAlPiUgY291bnQoc2VxdHlwZSwgc2l6ZV9jbGFzcywgSW5kZXgpCgpjb25mdXNpb25fdmlyX2NhbGxlZCRwZXJfdmlyYWwgPC0gMAoKZm9yIChpIGluIGMoMTpucm93KGNvbmZ1c2lvbl92aXJfY2FsbGVkKSkpIHsKICBjb25mdXNpb25fdmlyX2NhbGxlZCRwZXJfdmlyYWxbaV0gPC0gY29uZnVzaW9uX3Zpcl9jYWxsZWQkY291bnRbaV0vdHlwZV9jb3VudCRuW3R5cGVfY291bnQkc2VxdHlwZT09Y29uZnVzaW9uX3Zpcl9jYWxsZWQkc2VxdHlwZVtpXSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlX2NvdW50JEluZGV4PT1jb25mdXNpb25fdmlyX2NhbGxlZCRpbmRleFtpXSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGVfY291bnQkc2l6ZV9jbGFzcz09Y29uZnVzaW9uX3Zpcl9jYWxsZWQkc2l6ZVtpXV0qMTAwCn0KCmNvbmZ1c2lvbl92aXJfY2FsbGVkIDwtIGNvbmZ1c2lvbl92aXJfY2FsbGVkICU+JSBncm91cF9ieShzZXF0eXBlLCBzaXplKSAlPiUKICBzdW1tYXJpc2UobWVhbj1tZWFuKHBlcl92aXJhbCksIAogICAgICAgICAgICBzZD1zZChwZXJfdmlyYWwpKQoKY29uZnVzaW9uX3Zpcl9jYWxsZWQkc2l6ZSA8LSBmYWN0b3IoY29uZnVzaW9uX3Zpcl9jYWxsZWQkc2l6ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiMy01a2IiLCAiNS0xMGtiIiwgIj4xMGtiIikpCmBgYAoKYGBge3J9CmdncGxvdChjb25mdXNpb25fdmlyX2NhbGxlZCwgYWVzKHk9bWVhbiwgeD1zaXplLAogICAgICAgICAgICAgICAgICAgZmlsbD1zZXF0eXBlLAogICAgICAgICAgICAgICAgICAgY29sb3I9c2VxdHlwZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCkpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLXNkLCB5bWF4PW1lYW4rc2QpLCB3aWR0aD0uMiwKICAgICAgICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSguOSkpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMC41KSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDYpKSwgMSkpICsKICB4bGFiKCJMZW5ndGgiKSArCiAgeWxhYigiU2VxdWVuY2VzIENhbGxlZCBWaXJhbCAoJSkiKSAKYGBgCgoKCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfdmIgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlcyRrZWVwX3Njb3JlX3ZiX2R2ZiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfZHZmX3ZzMiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92cyA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IFQpCgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92c190diA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IFQpCgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92c190dl90bnYgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQpgYGAKCmBgYHtyfQp2aXJ1c2VzJHRydWVfdmlydXMgPC0gIm5vdCIKdmlydXNlcyR0cnVlX3ZpcnVzW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIl0gPC0gInZpcnVzIgoKdmlydXNlc19sb25nX3Njb3JlcyA8LSB2aXJ1c2VzICU+JSAKICBzZWxlY3QoY29udGFpbnMoImtlZXBfc2NvcmVfdmIiKSwgc2l6ZV9jbGFzcywgdHJ1ZV92aXJ1cykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9Y29udGFpbnMoImtlZXBfc2NvcmVfIiksIAogICAgICAgICAgICAgICBuYW1lc190bz0icnVsZV9jb21iaW5hdGlvbiIsCiAgICAgICAgICAgICAgIHZhbHVlc190bz0idmlyYWxfc2NvcmUiKSAlPiUgCiAgbXV0YXRlKHZpcmFsX3Njb3JlPWFzLmZhY3Rvcihyb3VuZCh2aXJhbF9zY29yZSkpKSAlPiUKICBncm91cF9ieShydWxlX2NvbWJpbmF0aW9uLCB2aXJhbF9zY29yZSwgc2l6ZV9jbGFzcywgdHJ1ZV92aXJ1cykgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpCgp2aXJ1c2VzX2xvbmdfc2NvcmVzJHNpemVfY2xhc3MgPC0gZmFjdG9yKHZpcnVzZXNfbG9uZ19zY29yZXMkc2l6ZV9jbGFzcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiMy01a2IiLCAiNS0xMGtiIiwgIj4xMGtiIikpCmBgYAoKYGBge3J9CnZpcnVzZXNfbG9uZ19zY29yZXNfYWRkaXRpb24gPC0gdmlydXNlc19sb25nX3Njb3Jlc1sodmlydXNlc19sb25nX3Njb3JlcyR0cnVlX3ZpcnVzPT0idmlydXMiICYgICh2aXJ1c2VzX2xvbmdfc2NvcmVzJHJ1bGVfY29tYmluYXRpb24hPSJrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnNfdHZfdG52IikgJiB2aXJ1c2VzX2xvbmdfc2NvcmVzJHZpcmFsX3Njb3JlIT0iMCIpLF0KZ2dwbG90KHZpcnVzZXNfbG9uZ19zY29yZXNfYWRkaXRpb24sIGFlcyh5PW4sIHg9cnVsZV9jb21iaW5hdGlvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9dmlyYWxfc2NvcmUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iCiAgKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQdXJwbGVzIikgKwogIHhsYWIoIiIpICsKICB5bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKyAKICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKCJWQiIsICJWQitEVkYiLCAiVkIrRFZGK1ZTMiIsICJWQitEVkYrVlMyK1ZTIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJWQitEVkYrVlMyK1ZTK2FkZGl0aW9uIikpICsKICBmYWNldF9ncmlkKH50cnVlX3ZpcnVzLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKYGBge3J9CmdncGxvdCh2aXJ1c2VzX2xvbmdfc2NvcmVzLCBhZXMoeT1uLCB4PXJ1bGVfY29tYmluYXRpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPXZpcmFsX3Njb3JlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUHVPciIsICkgKwogIHhsYWIoIiIpICsKICB5bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKyAKICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKCJWQiIsICJWQitEVkYiLCAiVkIrRFZGK1ZTMiIsICJWQitEVkYrVlMyK1ZTIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJWQitEVkYrVlMyK1ZTK2FkZGl0aW9uIiwgIlZCK0RWRitWUzIrVlMrYWRkaXRpb24tcmVtb3ZhbCIpKSArCiAgZmFjZXRfZ3JpZCh+dHJ1ZV92aXJ1cywgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlc19sb25nX3Njb3JlcywgYWVzKHk9biwgeD1ydWxlX2NvbWJpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD12aXJhbF9zY29yZSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICApICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlB1T3IiLCApICsKICB4bGFiKCIiKSArCiAgeWxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsgCiAgZmFjZXRfZ3JpZChzaXplX2NsYXNzfnRydWVfdmlydXMsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgoKIyBDb25zaWRlcmluZyBob3cgZWFjaCBtZXRob2QgY29udHJpYnV0ZXMgdG8gdGhlIGZpbmFsIHByZWRpY3Rpb24KYGBge3J9CnZpcnVzZXNfaGlnaCA8LSB2aXJ1c2VzW3ZpcnVzZXMka2VlcF9zY29yZV92Yl9kdmZfdnMyX3ZzX3R2Pj0xLF0gCnZpcnVzZXNfaGlnaF9tb2QgPC0gdmlydXNlc19oaWdoICU+JSBzZWxlY3Qoa2VlcF9zY29yZV92YixrZWVwX3Njb3JlX3ZiX2R2ZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VlcF9zY29yZV92Yl9kdmZfdnMyLCBrZWVwX3Njb3JlX3ZiX2R2Zl92czJfdnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXBfc2NvcmVfdmJfZHZmX3ZzMl92c190diwga2VlcF9zY29yZV92Yl9kdmZfdnMyX3ZzX3R2X3RudikKI3ZpcnVzZXNfaGlnaF9tb2QgPC0gYXBwbHkodmlydXNlc19oaWdoX21vZCwgYygxLDIpLCBmdW5jdGlvbih4KSB7aWYgKHggPj0gMSkge3ggPC0gMX0gZWxzZSB7eCA8LSAwfX0pCnZpcnVzZXNfaGlnaF9tb2QgPC0gYXNfdGliYmxlKHZpcnVzZXNfaGlnaF9tb2QpCgoKYGBgCgpgYGB7cn0Kc21fbSA8LSByZXNoYXBlMjo6bWVsdCh2aXJ1c2VzX2hpZ2hfbW9kKQpjb2xuYW1lcyhzbV9tKSA8LSBjKCJtZXRob2QiLCAidmlyYWxfc2NvcmUiKQpgYGAKCmBgYHtyfQpzbV9tIDwtIHNtX21bc21fbSR2aXJhbF9zY29yZT4wLF0KCnNtX20kc2NvcmUgPC0gc21fbSR2aXJhbF9zY29yZQoKc21fbSRzY29yZVtzbV9tJHZpcmFsX3Njb3JlPT0wLjVdIDwtICIwLjUiCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT49MV0gPC0gIjEiCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT49Ml0gPC0gIjIiCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT49M10gPC0gIjMiCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT49NF0gPC0gIjQiCnNtX20kc2NvcmVbc21fbSR2aXJhbF9zY29yZT49NV0gPC0gIjUiCgpzbV9tJHNjb3JlIDwtIGZhY3RvcihzbV9tJHNjb3JlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIjAuNSIsICIxIiwgIjIiLCIzIiwiNCIsIjUiKSkKYGBgCgoKYGBge3J9CmdncGxvdChzbV9tLCBhZXMoeD1tZXRob2QsIHk9c2NvcmUsCiAgICAgICAgICAgICAgICAgICBmaWxsPXNjb3JlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAnVmlyYWwgU2NvcmUnLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShjKHZpcmlkaXMoNikpLCAxKSkgKwogIHhsYWIoIiIpICsKICB5bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKIyMgQW5vdGhlciB3YXkgb2YgdmlzdWFsaXppbmcgYmV0d2VlbiBydWxlIHNldHMKCmBgYHtyfQp2aXJ1c2VzX21jY19hbGx1dmlhbCA8LSBkYXRhLmZyYW1lKHNlcXR5cGU9dmlydXNlcyRzZXF0eXBlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlZXBfc2NvcmVfaGlnaF9NQ0M9dmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0M9dmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKQoKCnZpcnVzZXNfbWNjX2FsbHV2aWFsJGtlZXBfc2NvcmVfdmIgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlc19tY2NfYWxsdXZpYWwka2VlcF9zY29yZV9kdmYgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQoKdmlydXNlc19tY2NfYWxsdXZpYWwka2VlcF9zY29yZV92cyA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IFQpCgp2aXJ1c2VzX21jY19hbGx1dmlhbCRrZWVwX3Njb3JlX3ZzMiA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCgp2aXJ1c2VzX21jY19hbGx1dmlhbCRrZWVwX3Njb3JlX3R2IDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKCnZpcnVzZXNfbWNjX2FsbHV2aWFsJGtlZXBfc2NvcmVfdG52IDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKYGBgCgpgYGB7cn0KdmlydXNlc19tY2NfYWxsdXZpYWwgJT4lCiAgY291bnQoc2VxdHlwZSwga2VlcF9zY29yZV9oaWdoX01DQykgJT4lIAogIHNwcmVhZChrZXkgPSBrZWVwX3Njb3JlX2hpZ2hfTUNDLCB2YWx1ZT1uKQpgYGAKCgpgYGB7cn0KdmlydXNlc19tY2NfYWxsdXZpYWwgPC0gdmlydXNlc19tY2NfYWxsdXZpYWwgJT4lCiAgY291bnQoc2VxdHlwZSwga2VlcF9zY29yZV9kdmYsIGtlZXBfc2NvcmVfdmIsIGtlZXBfc2NvcmVfdnMsCiAgICAgICAga2VlcF9zY29yZV92czIsIGtlZXBfc2NvcmVfdHYsIGtlZXBfc2NvcmVfdG52LCBrZWVwX3Njb3JlX2hpZ2hfTUNDKSAlPiUKICBtdXRhdGUoaGlnaF9tY2NfdmlyYWxfc2NvcmU9ZmFjdG9yKHJvdW5kKGtlZXBfc2NvcmVfaGlnaF9NQ0MpKSkKYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXNfbWNjX2FsbHV2aWFsLAogICAgICAgYWVzKGF4aXMxID0ga2VlcF9zY29yZV9kdmYsIGF4aXMyID0ga2VlcF9zY29yZV92YiwgCiAgICAgICAgICAgYXhpczMgPSBrZWVwX3Njb3JlX3ZzLCBheGlzNCA9IGtlZXBfc2NvcmVfdnMyLCAKICAgICAgICAgICBheGlzNSA9IGtlZXBfc2NvcmVfdHYsIGF4aXM2ID0ga2VlcF9zY29yZV90bnYsIAogICAgICAgICAgIHk9bikpICsKICBnZW9tX2FsbHV2aXVtKGFlcyhmaWxsPWhpZ2hfbWNjX3ZpcmFsX3Njb3JlKSwKICAgICAgICAgICAgICAgIHdpZHRoID0gMCwga25vdC5wb3MgPSAwLCByZXZlcnNlID0gRkFMU0UpICsKICBnZW9tX3N0cmF0dW0od2lkdGggPSAxLzUpICsKICB0aGVtZV9idygpICsKICBnZW9tX3RleHQoc3RhdCA9ICJzdHJhdHVtIiwgYWVzKGxhYmVsID0gYWZ0ZXJfc3RhdChzdHJhdHVtKSksCiAgICAgICAgICAgIHJldmVyc2UgPSBGQUxTRSkgKwogIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKQogICAgICAgICkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9YygxLDIsMyw0LDUsNiksCiAgICBsYWJlbHM9YygiZHZmIiwgImtqIiwgInZzIiwgInZzMiIsCiAgICAgICAgICAgICAidHYiLCAidG52IikpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlB1T3IiLCApICsKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXM9ImZyZWVfeSIpIApgYGAgCgoKCgoKCiMjIFZpc3VhbGl6aW5nIGNvbmZ1c2lvbiBtYXRyaXggYnkgbnVtYmVyIG9mIHRvb2xzCgoKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemUgPC0gdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPjFdIDwtICI+IDEiCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPT0xXSA8LSAiMSIKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVt2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M9PTAuNV0gPC0gIjAuNSIKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVt2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9NQ0M9PTBdIDwtICIwIgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplW3ZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQz09LTAuNV0gPC0gIi0wLjUiCnZpcnVzZXMka2VlcF9zY29yZV92aXN1YWxpemVbdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfTUNDPT0tMV0gPC0gIi0xIgp2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplW3ZpcnVzZXMka2VlcF9zY29yZV9oaWdoX01DQzw9LTFdIDwtICI8IC0xIgoKdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSA8LSBmYWN0b3IodmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCI8IC0xIiwgIi0xIiwgIi0wLjUiLCAiMCIsICIwLjUiLCIxIiwgIj4gMSIpKQojdmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSA8LSBmYWN0b3IodmlydXNlcyRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSwgCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9Yygi4omkIDAiLCAi4omkIDAiLCAi4omkIDAiLCAiMC41IiwiMSIsICI+IDEiKSkKYGBgCgpgYGB7cn0KbGV2ZWxzKGZhY3Rvcih2aXJ1c2VzJGtlZXBfc2NvcmVfdmlzdWFsaXplKSkKYGBgCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWFzLmZhY3RvcihJbmRleCksCiAgICAgICAgICAgICAgICAgICBmaWxsPWtlZXBfc2NvcmVfdmlzdWFsaXplLCBjb2xvcj1rZWVwX3Njb3JlX3Zpc3VhbGl6ZSkpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHBvc2l0aW9uPSJzdGFjayIpICsKICB0aGVtZV9saWdodCgpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KQogICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gJ1ZpcmFsIFNjb3JlJywKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEoYyh2aXJpZGlzKDYpKSwgMSkpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gJ1ZpcmFsIFNjb3JlJywKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEoYyh2aXJpZGlzKDYpKSwgMC41KSkgKwogIHhsYWIoIkluZGV4IikgKwogIHlsYWIoIlNlcXVlbmNlIENvdW50IikgKwogIGZhY2V0X3dyYXAofmNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MsIHNjYWxlcyA9ICJmcmVlIikKCmBgYAoKIyMgY2x1c3RlcmluZwpgYGB7cn0KdmlyYWxfc2NvcmVzIDwtIG1hdHJpeChkYXRhPTAsIG5yb3c9bnJvdyh2aXJ1c2VzKSwgbmNvbD1ucm93KGNvbWJvc19saXN0KSkKbnVtX3ZpcnVzZXMgPC0gZGF0YS5mcmFtZSh0b29sY29tYm89cmVwKDAsIG5yb3coY29tYm9zX2xpc3QpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fdmlydXNlcz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkpKQoKZm9yIChpIGluIDE6bnJvdyhjb21ib3NfbGlzdCkpIHsKICB2aXJhbF9zY29yZXNbLGldIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV92aWJyYW50ID0gY29tYm9zX2xpc3QkVklCUkFOVFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IGNvbWJvc19saXN0JFZTW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IGNvbWJvc19saXN0JFZTMltpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IGNvbWJvc19saXN0JHR1bmVfdmlyYWxbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gY29tYm9zX2xpc3QkdHVuZV9ub3RfdmlyYWxbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gY29tYm9zX2xpc3QkRFZGW2ldKQogIAogIGlmIChtYXgodmlyYWxfc2NvcmVzWyxpXSk8PTApIHsKICAgIG51bV92aXJ1c2VzJG51bV92aXJ1c2VzW2ldIDwtIDAKICB9CiAgZWxzZSB7CiAgICBudW1fdmlydXNlcyRudW1fdmlydXNlc1tpXSA8LSB0YWJsZSh2aXJhbF9zY29yZXNbLGldPj0xKVtbMl1dCiAgfQogIAogIG51bV92aXJ1c2VzJHRvb2xjb21ib1tpXSA8LSBjb21ib3NfbGlzdCR0b29sY29tYm9baV0KICAKICBudW1fdmlydXNlcyR0b29sY29tYm8yW2ldIDwtIGNvbWJvc19saXN0JHRvb2xjb21ibzJbaV0KfQoKbnVtX3ZpcnVzZXMkbnVtcnVsZXMgPC0gc3RyX2NvdW50KG51bV92aXJ1c2VzJHRvb2xjb21ibywgIjEiKQpudW1fdmlydXNlcyA8LSBudW1fdmlydXNlc1tvcmRlcihudW1fdmlydXNlcyRudW1fdmlydXNlcywgZGVjcmVhc2luZz1GKSxdCm51bV92aXJ1c2VzJHRvb2xjb21ibyA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvLCBsZXZlbHMgPSB1bmlxdWUobnVtX3ZpcnVzZXMkdG9vbGNvbWJvKSkKbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiwgbGV2ZWxzID0gdW5pcXVlKG51bV92aXJ1c2VzJHRvb2xjb21ibzIpKQpudW1fdmlydXNlcyRudW1ydWxlcyA8LSBhcy5mYWN0b3IobnVtX3ZpcnVzZXMkbnVtcnVsZXMpCmBgYAoKCgpgYGB7cn0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzW3Jvd1N1bXModmlyYWxfc2NvcmVzKT4wLF0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzX25vemVyb3MgKyAxCnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIGFzLmRhdGEuZnJhbWUodmlyYWxfc2NvcmVzX25vemVyb3MpCgpjb2xuYW1lcyh2aXJhbF9zY29yZXNfbm96ZXJvcykgPC0gbnVtX3ZpcnVzZXMkdG9vbGNvbWJvCmBgYAoKYGBge3J9CmxpYnJhcnkocGh5bG9zZXEpCmBgYAoKCmBgYHtyfQp0b29sZGF0YSA8LSBudW1fdmlydXNlcwoKcm93bmFtZXModG9vbGRhdGEpIDwtIHRvb2xkYXRhJHRvb2xjb21ibwpgYGAKCmBgYHtyfQpwaHlzZXFfcG9vbGVkIDwtIHBoeWxvc2VxKG90dV90YWJsZSh2aXJhbF9zY29yZXNfbm96ZXJvcywgdGF4YV9hcmVfcm93cyA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX2RhdGEodG9vbGRhdGEpKQpgYGAKCmBgYHtyfQpvcmRpbmF0aW9uIDwtIHBoeWxvc2VxOjpvcmRpbmF0ZShwaHlzZXEgPXBoeXNlcV9wb29sZWQsIG1ldGhvZCA9ICJQQ29BIiwgZGlzdGFuY2UgPSAiYnJheSIpCnBoeWxvc2VxOjpwbG90X29yZGluYXRpb24ocGh5c2VxID0gcGh5c2VxX3Bvb2xlZCwgb3JkaW5hdGlvbiA9IG9yZGluYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGU9Im51bXJ1bGVzIiwgY29sb3I9Im51bV92aXJ1c2VzIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgdGhlbWVfYncoKSArCiAgZ2VvbV9sYWJlbChsYWJlbD10b29sZGF0YSR0b29sY29tYm8pCgpwaHlsb3NlcTo6cGxvdF9vcmRpbmF0aW9uKHBoeXNlcSA9IHBoeXNlcV9wb29sZWQsIG9yZGluYXRpb24gPSBvcmRpbmF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPSJudW1ydWxlcyIsIGNvbG9yPSJudW1fdmlydXNlcyIpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHRoZW1lX2J3KCkKYGBgCgpgYGB7cn0KYnJheV9kaXN0IDwtIHBoeWxvc2VxOjpkaXN0YW5jZShwaHlzZXFfcG9vbGVkLCBtZXRob2Q9ImJyYXkiKQpjbHVzdGVycyA8LSBoY2x1c3QoZGlzdChicmF5X2Rpc3QpKQpwbG90KGNsdXN0ZXJzKQoKbXljbHVzdGVycyA8LSBjdXRyZWUoY2x1c3RlcnMsIGg9MC44KQpgYGAKCgpgYGB7cn0KI25hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09MV0pCiNuYW1lcyhteWNsdXN0ZXJzW215Y2x1c3RlcnM9PTJdKQojbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0zXSkKI25hbWVzKG15Y2x1c3RlcnNbbXljbHVzdGVycz09NF0pCiNuYW1lcyhteWNsdXN0ZXJzW215Y2x1c3RlcnM9PTVdKQoKbXljbHVzdGVyc19kZiA8LSB0aWJibGUoY29tYm89bmFtZXMobXljbHVzdGVycyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2luZGV4PW15Y2x1c3RlcnMpCgpteWNsdXN0ZXJzX2RmIDwtIHNlcGFyYXRlKG15Y2x1c3RlcnNfZGYsIGNvbD1jb21ibywgaW50bz1jKCJ0bnYiLCAiRFZGIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInR2IiwgIlZCIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlZTIiwgIlZTMiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlcD0iICIsIHJlbW92ZSA9IEYpCgoKdG9vbF9jb3VudCA8LSBhcy5kYXRhLmZyYW1lKHJiaW5kKHRhYmxlKG15Y2x1c3RlcnNfZGYkdG52LCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJERWRiwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiR0diwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRWQiwgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRWUywgbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KVsyLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgdGFibGUobXljbHVzdGVyc19kZiRWUzIsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdKQogICAgICAgICAgICAgICAgICAgICkKCnRvb2xfY291bnQgPC0gZGF0YS5mcmFtZSh0KGFwcGx5KHRvb2xfY291bnQsIGMoMSksIGZ1bmN0aW9uKHgpIHt4IDwtIHgvdGFibGUobXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4KX0pKSkKCgoKdG9vbF9jb3VudCRtZXRob2QgPC0gYygidG52IiwgIkRWRiIsICJ0diIsICJWQiIsICJWUyIsICJWUzIiKQoKdG9vbF9jb3VudCA8LSBtZWx0KHRvb2xfY291bnQpCgpjb2xuYW1lcyh0b29sX2NvdW50KSA8LSBjKCJ0b29sIiwgImNsdXN0ZXJfaW5kZXgiLCAidG9vbF9jb3VudF9ub3JtIikKYGBgCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCgpnZ3Bsb3QodG9vbF9jb3VudCwgYWVzKHg9dG9vbCwgeT10b29sX2NvdW50X25vcm0sCiAgICAgICAgICAgICAgICAgICBmaWxsPXRvb2wsCiAgICAgICAgICAgICAgICAgICBjb2xvcj10b29sKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgI2xlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNikpLCAxKSkgKwogIHhsYWIoIlRvb2wiKSArCiAgeWxhYigiUHJvcG9ydGlvbiBvZiBUaW1lcyBpbiBDbHVzdGVyIikgKyAKICBmYWNldF93cmFwKH5jbHVzdGVyX2luZGV4LCBucm93PTEpCmBgYAoKYGBge3J9CmFjY3VyYWN5X3Njb3Jlc19tZWx0IDwtIGFjY3VyYWN5X3Njb3JlcyAlPiUgCiAgc2VsZWN0KHByZWNpc2lvbiwgcmVjYWxsLCBNQ0MsIG51bXJ1bGVzLCB0b29sY29tYm8pICU+JQogIGdyb3VwX2J5KG51bXJ1bGVzLCB0b29sY29tYm8pICU+JQogIHN1bW1hcmlzZShwcmVjaXNpb249bWVhbihwcmVjaXNpb24pLAogICAgICAgICAgICByZWNhbGw9bWVhbihyZWNhbGwpLAogICAgICAgICAgICBNQ0M9bWVhbihNQ0MpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1jKHByZWNpc2lvbiwgcmVjYWxsLCBNQ0MpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89InBlcmZvcm1hbmNlX21ldHJpYyIsCiAgICAgICAgICAgICAgIHZhbHVlc190bz0icGVyZm9ybWFuY2VfbWV0cmljX3Njb3JlIikKYGBgCgoKYGBge3J9Cm15Y2x1c3RlcnNfZGYgPC0gaW5uZXJfam9pbihhY2N1cmFjeV9zY29yZXNfbWVsdCwgbXljbHVzdGVyc19kZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBieT1jKCJ0b29sY29tYm8iPSJjb21ibyIpKQoKbXljbHVzdGVyc19kZiRjbHVzdGVyX2luZGV4IDwtIGFzLmZhY3RvcihteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpCmBgYAoKYGBge3J9CmdncGxvdChteWNsdXN0ZXJzX2RmLCBhZXMoeD1jbHVzdGVyX2luZGV4LCB5PXBlcmZvcm1hbmNlX21ldHJpY19zY29yZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1jbHVzdGVyX2luZGV4LCBmaWxsPWNsdXN0ZXJfaW5kZXgpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC41KSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHlsYWIoIk1DQyIpICsKICB4bGFiKCJDbHVzdGVyIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoOSkpLCAwLjUpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoOSkpLCAxKSkgKwogIGZhY2V0X3dyYXAofnBlcmZvcm1hbmNlX21ldHJpYykKYGBgCgojIyBhbGwgNiB0b29scyBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gVCkKYGBgCgoKYGBge3J9CnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGwgPC0gInRydWUgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGxbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2FsbDwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGxbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2FsbD49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9hbGxbdmlydXNlcyRzZXF0eXBlIT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2FsbD49MV0gPC0gImZhbHNlIHBvc2l0aXZlIgpgYGAKCmBgYHtyfQpUUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsKVs0XQpGUCA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsKVsyXQpUTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsKVszXQpGTiA8LSB0YWJsZSh2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsKVsxXQoKcHJlY2lzaW9uIDwtIFRQLyhUUCtGUCkKcHJlY2lzaW9uCnJlY2FsbCA8LSBUUC8oVFArRk4pCnJlY2FsbApGMSA8LSAyKnByZWNpc2lvbipyZWNhbGwvKHByZWNpc2lvbityZWNhbGwpCkYxCgpNQ0MgPC0gKFRQKlROLUZQKkZOKS9zcXJ0KGFzLm51bWVyaWMoVFArRlApKmFzLm51bWVyaWMoVFArRk4pKmFzLm51bWVyaWMoVE4rRlApKmFzLm51bWVyaWMoVE4rRk4pKQpNQ0MKYGBgCnByZWNpc2lvbj02MiUsIHJlY2FsbD05MiUsIE1DQz03MyUKCgp2aXN1YWxpemluZyBjb25mdXNpb24gbWF0cml4IGJ5IHRheGEKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhIDwtIG1lbHQodGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbCwgdmlydXNlcyRzZXF0eXBlLCB2aXJ1c2VzJEluZGV4KSkKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsIkluZGV4IiwgImNvdW50IikKYGBgCgpgYGB7cn0KdGFibGUodmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbCkKCmxlbmd0aChncmVwKCJ0cnVlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2FsbCkpL25yb3codmlydXNlcykKYGBgCgpgYGB7cn0KbGVuZ3RoKGdyZXAoInRydWUgcG9zaXRpdmUiLCB2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfYWxsKSkvbGVuZ3RoKGdyZXAoInZpcnVzIiwgdmlydXNlcyRzZXF0eXBlKSkKYGBgCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCmBgYAoKYGBge3J9CmdncGxvdChjb25mdXNpb25fYnlfdGF4YSwgYWVzKHg9Y291bnQsIHk9YXMuZmFjdG9yKEluZGV4KSwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeCwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXgpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgojIyBoaWdoIHJlY2FsbCBleGFtcGxlCmBgYHtyfQp2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGwgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBUKQpgYGAKCgpgYGB7cn0KdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsIDwtICJ0cnVlIG5lZ2F0aXZlIgp2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGxbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiICYgdmlydXNlcyRrZWVwX3Njb3JlX2hpZ2hfcmVjYWxsPDFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3JlY2FsbD49MV0gPC0gInRydWUgcG9zaXRpdmUiCnZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbFt2aXJ1c2VzJHNlcXR5cGUhPSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfaGlnaF9yZWNhbGw+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKYGBgCgoKdmlzdWFsaXppbmcgY29uZnVzaW9uIG1hdHJpeCBieSB0YXhhCmBgYHtyfQpjb25mdXNpb25fYnlfdGF4YSA8LSBtZWx0KHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCwgdmlydXNlcyRzZXF0eXBlLCB2aXJ1c2VzJEluZGV4KSkKY29sbmFtZXMoY29uZnVzaW9uX2J5X3RheGEpIDwtIGMoImNvbmZ1c2lvbl9tYXRyaXgiLCAic2VxdHlwZSIsIkluZGV4IiwgImNvdW50IikKYGBgCgoKCmBgYHtyfQpwYWwgPC0gZ2d0aGVtZXM6OnRhYmxlYXVfY29sb3JfcGFsKHBhbGV0dGU9IlRhYmxlYXUgMTAiLCB0eXBlPSJyZWd1bGFyIikKYGBgCgpgYGB7cn0KcDIgPC0gZ2dwbG90KGNvbmZ1c2lvbl9ieV90YXhhLCBhZXMoeD1jb3VudCwgeT1hcy5mYWN0b3IoSW5kZXgpLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCnAyCmBgYAoKYWNjdXJhY3k6CmBgYHtyfQpsZW5ndGgoZ3JlcCgidHJ1ZSIsIHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCkpL25yb3codmlydXNlcykKYGBgCjAuODg3CgpyZWNhbGwKYGBge3J9Cmxlbmd0aChncmVwKCJ0cnVlIHBvc2l0aXZlIiwgdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsKSkvbGVuZ3RoKGdyZXAoInZpcnVzIiwgdmlydXNlcyRzZXF0eXBlKSkKYGBgCnJlY292ZXIgYWxtb3N0IGFsbCBvZiB0aGUgdmlydXNlcyB0aGlzIHdheSwgYnV0IG1vcmUgcHJvdGlzdCBjb250YW1pbmF0aW9uCgowLjk2MAoKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhIDwtIHZpcnVzZXMgJT4lIGNvdW50KGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwsIHNlcXR5cGUsIHNpemVfY2xhc3MpCgpjb2xuYW1lcyhjb25mdXNpb25fYnlfdGF4YSkgPC0gYygiY29uZnVzaW9uX21hdHJpeCIsICJzZXF0eXBlIiwic2l6ZSIsICJjb3VudCIpCmBgYAoKCgojIyBmZXcgdG9vbHMsIGhpZ2ggTUNDIGV4YW1wbGUKYGBge3J9CnZpcnVzZXMka2VlcF9zY29yZV9mZXdfdG9vbHMgPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aWJyYW50ID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyMiA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ192aXJhbCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3R1bmluZ19ub3RfdmlyYWwgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIgPSBGKQpgYGAKCgpgYGB7cn0KdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2Zld190b29scyA8LSAidHJ1ZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2Zld190b29sc1t2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfZmV3X3Rvb2xzPDFdIDwtICJmYWxzZSBuZWdhdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2Zld190b29sc1t2aXJ1c2VzJHNlcXR5cGU9PSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfZmV3X3Rvb2xzPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKdmlydXNlcyRjb25mdXNpb25fbWF0cml4X2Zld190b29sc1t2aXJ1c2VzJHNlcXR5cGUhPSJ2aXJ1cyIgJiB2aXJ1c2VzJGtlZXBfc2NvcmVfZmV3X3Rvb2xzPj0xXSA8LSAiZmFsc2UgcG9zaXRpdmUiCmBgYAoKYGBge3J9ClRQIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHMpWzRdCkZQIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHMpWzJdClROIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHMpWzNdCkZOIDwtIHRhYmxlKHZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9mZXdfdG9vbHMpWzFdCgpwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQpwcmVjaXNpb24KcmVjYWxsIDwtIFRQLyhUUCtGTikKcmVjYWxsCkYxIDwtIDIqcHJlY2lzaW9uKnJlY2FsbC8ocHJlY2lzaW9uK3JlY2FsbCkKRjEKCk1DQyA8LSAoVFAqVE4tRlAqRk4pL3NxcnQoYXMubnVtZXJpYyhUUCtGUCkqYXMubnVtZXJpYyhUUCtGTikqYXMubnVtZXJpYyhUTitGUCkqYXMubnVtZXJpYyhUTitGTikpCk1DQwpgYGAKcHJlY2lzaW9uPTc3JSwgcmVjYWxsPTc2JSwgTUNDPTc0JQoKCiMjIFZpc3VhbGl6aW5nIHRoZSBkaWZmZXJlbnQgc2V0cwoKYGBge3J9CmNvbmZ1c2lvbl9ieV90YXhhX21ldGhvZCA8LSB2aXJ1c2VzICU+JSAKICBzZWxlY3QoY29udGFpbnMoImNvbmZ1c2lvbl9tYXRyaXgiKSwgc2VxdHlwZSwgSW5kZXgpICU+JQogIHBpdm90X2xvbmdlcihjb2xzPWNvbnRhaW5zKCJjb25mdXNpb25fbWF0cml4IiksIAogICAgICAgICAgICAgICBuYW1lc190bz0iY29uZnVzaW9uX21hdHJpeF90eXBlIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvPSJjb25mdXNpb25fbWF0cml4X3ZhbHVlIikgJT4lCiAgY291bnQoc2VxdHlwZSwgSW5kZXgsIGNvbmZ1c2lvbl9tYXRyaXhfdHlwZSwgY29uZnVzaW9uX21hdHJpeF92YWx1ZSkKYGBgCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCmBgYAoKYGBge3J9CmdncGxvdChjb25mdXNpb25fYnlfdGF4YV9tZXRob2QsIGFlcyh5PW4sIHg9Y29uZnVzaW9uX21hdHJpeF90eXBlLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X3ZhbHVlLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF92YWx1ZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIFNlcXVlbmNlcyIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgpgYGB7cn0KY29uZnVzaW9uX2J5X3RheGFfbWV0aG9kIDwtIHZpcnVzZXMgJT4lIAogIHNlbGVjdChjb250YWlucygiY29uZnVzaW9uX21hdHJpeCIpLCBzZXF0eXBlLCBJbmRleCkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9Y29udGFpbnMoImNvbmZ1c2lvbl9tYXRyaXgiKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvPSJjb25mdXNpb25fbWF0cml4X3R5cGUiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89ImNvbmZ1c2lvbl9tYXRyaXhfdmFsdWUiKSAlPiUKICBjb3VudChzZXF0eXBlLCBJbmRleCwgY29uZnVzaW9uX21hdHJpeF90eXBlLCBjb25mdXNpb25fbWF0cml4X3ZhbHVlKSAlPiUKICBmaWx0ZXIoZ3JlcGwoInRydWUiLCBjb25mdXNpb25fbWF0cml4X3ZhbHVlKSkgJT4lCiAgbXV0YXRlKGNvbmZ1c2lvbl9tYXRyaXhfdHlwZT1zdWIoImNvbmZ1c2lvbl9tYXRyaXhfIiwgIiIsIGNvbmZ1c2lvbl9tYXRyaXhfdHlwZSkpCmBgYAoKYGBge3J9CmdncGxvdChjb25mdXNpb25fYnlfdGF4YV9tZXRob2QsIGFlcyh5PW4sIHg9Y29uZnVzaW9uX21hdHJpeF90eXBlLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X3ZhbHVlLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF92YWx1ZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTEwLCBhbmdsZT05MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKVszOjRdLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSlbMzo0XSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiVG9vbCBTZXQiKSArCiAgeWxhYigiQ29udGlnIENvdW50IikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKIyMgYW5vdGhlciB3YXkgb2YgdmlzdWFsaXppbmcgdGhlIGRpZmZlcmVudCB0b29sIHNldHMgc2NvcmVzCmBgYHtyfQp2aXJ1c2VzJHRydWVfdmlydXMgPC0gIm5vdCIKdmlydXNlcyR0cnVlX3ZpcnVzW3ZpcnVzZXMkc2VxdHlwZT09InZpcnVzIl0gPC0gInZpcnVzIgoKdmlydXNlc19sb25nX3Njb3JlcyA8LSB2aXJ1c2VzICU+JSAKICBzZWxlY3QoY29udGFpbnMoImtlZXBfc2NvcmVfaGlnaCIpLCBjb250YWlucygia2VlcF9zY29yZV9hbGwiKSwgc2l6ZV9jbGFzcywgdHJ1ZV92aXJ1cykgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHM9Y29udGFpbnMoImtlZXBfc2NvcmVfIiksIAogICAgICAgICAgICAgICBuYW1lc190bz0icnVsZV9jb21iaW5hdGlvbiIsCiAgICAgICAgICAgICAgIHZhbHVlc190bz0idmlyYWxfc2NvcmUiKSAlPiUgCiAgbXV0YXRlKHZpcmFsX3Njb3JlPWFzLmZhY3Rvcihyb3VuZCh2aXJhbF9zY29yZSkpKSAlPiUKICBncm91cF9ieShydWxlX2NvbWJpbmF0aW9uLCB2aXJhbF9zY29yZSwgc2l6ZV9jbGFzcywgdHJ1ZV92aXJ1cykgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpCgp2aXJ1c2VzX2xvbmdfc2NvcmVzJHNpemVfY2xhc3MgPC0gZmFjdG9yKHZpcnVzZXNfbG9uZ19zY29yZXMkc2l6ZV9jbGFzcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiMy01a2IiLCAiNS0xMGtiIiwgIj4xMGtiIikpCmBgYAoKYGBge3J9CmdncGxvdCh2aXJ1c2VzX2xvbmdfc2NvcmVzLCBhZXMoeT1uLCB4PXJ1bGVfY29tYmluYXRpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPXZpcmFsX3Njb3JlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUHVPciIsICkgKwogIHhsYWIoIiIpICsKICB5bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKyAKICBmYWNldF9ncmlkKH50cnVlX3ZpcnVzLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKYGBge3J9CmdncGxvdCh2aXJ1c2VzX2xvbmdfc2NvcmVzLCBhZXMoeT1uLCB4PXJ1bGVfY29tYmluYXRpb24sCiAgICAgICAgICAgICAgICAgICBmaWxsPXZpcmFsX3Njb3JlKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUHVPciIsICkgKwogIHhsYWIoIiIpICsKICB5bGFiKCJOdW1iZXIgb2YgU2VxdWVuY2VzIikgKyAKICBmYWNldF9ncmlkKHNpemVfY2xhc3N+dHJ1ZV92aXJ1cywgc2NhbGVzID0gImZyZWUiKQpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCkV4dHJhIFN0dWZmCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKYGBge3J9CmdncGxvdCh2aXJ1c2VzLCBhZXMoeD1jaGVja3ZfbGVuZ3RoLCB5PWtlZXBfc2NvcmVfaGlnaF9NQ0MsCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9NQ0MsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfTUNDKSkgKwogIGdlb21fcG9pbnQoc3RhdD0iaWRlbnRpdHkiLCBzaGFwZT0yMSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIlNlcXVlbmNlIExlbmd0aCAoYnApIikgKwogIHlsYWIoIlBpcGVsaW5lIFZpcmFsIFNjb3JlIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlKSArIAogIHNjYWxlX3hfbG9nMTAoKQpgYGAKCgpgYGB7cn0KZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWNoZWNrdl9jb21wbGV0ZW5lc3MsIHk9aGFsbG1hcmssCiAgICAgICAgICAgICAgICAgICBmaWxsPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9yZWNhbGwsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsKSkgKwogIGdlb21fcG9pbnQoc3RhdD0iaWRlbnRpdHkiLCBzaGFwZT0yMSkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIkNoZWNrViBDb21wbGV0ZW5lc3MiKSArCiAgeWxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlKSArIAogIHNjYWxlX3hfbG9nMTAoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlcywgYWVzKHg9Y2hlY2t2X2NvbXBsZXRlbmVzcywgeT1rZWVwX3Njb3JlX2hpZ2hfTUNDLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCkpICsKICBnZW9tX3BvaW50KHN0YXQ9ImlkZW50aXR5Iiwgc2hhcGU9MjEpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJDaGVja1YgQ29tcGxldGVuZXNzIikgKwogIHlsYWIoIlBpcGVsaW5lIFZpcmFsIFNjb3JlIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlKSArIAogIHNjYWxlX3hfbG9nMTAoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlcywgYWVzKHg9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCwgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfcmVjYWxsLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3JlY2FsbCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiU2VxdWVuY2UgTGVuZ3RoIChicCkiKSArCiAgeWxhYigiUGlwZWxpbmUgVmlyYWwgU2NvcmUiKSArCiAgc2NhbGVfeV9sb2cxMCgpCmBgYAoKCgpsb29raW5nIGF0IGZhbHNlIG5lZ2F0aXZlcwpgYGB7cn0KdmlydXNlc19mYWxzZV9uZWdzIDwtIHZpcnVzZXNbKHZpcnVzZXMkc2VxdHlwZT09InZpcnVzIiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3JlY2FsbDwxKSxdCmBgYAoKbG9va2luZyBhdCBwcm90aXN0cyBjYWxsaW5nIHZpcmFsCmBgYHtyfQp2aXJ1c2VzX2ZhbHNlX3Bvc19wcm90aXN0cyA8LSB2aXJ1c2VzWyh2aXJ1c2VzJHNlcXR5cGU9PSJwcm90aXN0IiAmIHZpcnVzZXMka2VlcF9zY29yZV9oaWdoX3JlY2FsbD49MSksXQpgYGAKCgoKCgoKCgoKCgojIENvbnNpZGVyaW5nIGhvdyBlYWNoIG1ldGhvZCBjb250cmlidXRlcyB0byB0aGUgZmluYWwgcHJlZGljdGlvbiAoaGlnaCBNQ0MpCgpgYGB7cn0KdmlydXNlcyRrZWVwX3Njb3JlX3ZiIDwtIGdldHRpbmdfdmlyYWxfc2V0XzEodmlydXNlcywgaW5jbHVkZV9kZWVwdmlyZmluZGVyID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlicmFudCA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlcjIgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfdmlyYWwgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV90dW5pbmdfbm90X3ZpcmFsID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gRikKCnZpcnVzZXMka2VlcF9zY29yZV92Yl90diA8LSBnZXR0aW5nX3ZpcmFsX3NldF8xKHZpcnVzZXMsIGluY2x1ZGVfZGVlcHZpcmZpbmRlciA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpYnJhbnQgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX3ZpcmFsID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nX25vdF92aXJhbCA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX3ZpcnNvcnRlciA9IEYpCmBgYAoKYGBge3J9CnZpcnVzZXNfaGlnaCA8LSB2aXJ1c2VzW3ZpcnVzZXMka2VlcF9zY29yZV92Yl90dj49MSxdICN1bmNvbW1lbnQgdGhpcyBsaW5lIGlmIHdhbnQgdG8gdXNlIGFsbCA2IHRvb2xzCnZpcnVzZXNfaGlnaF9tb2QgPC0gdmlydXNlc19oaWdoICU+JSBzZWxlY3Qoa2VlcF9zY29yZV92YiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VlcF9zY29yZV92Yl90dikKI3ZpcnVzZXNfaGlnaF9tb2QgPC0gYXBwbHkodmlydXNlc19oaWdoX21vZCwgYygxLDIpLCBmdW5jdGlvbih4KSB7aWYgKHggPj0gMSkge3ggPC0gMX0gZWxzZSB7eCA8LSAwfX0pCnZpcnVzZXNfaGlnaF9tb2QgPC0gYXNfdGliYmxlKHZpcnVzZXNfaGlnaF9tb2QpCgoKYGBgCgpgYGB7cn0Kc21fbSA8LSByZXNoYXBlMjo6bWVsdCh2aXJ1c2VzX2hpZ2hfbW9kKQpjb2xuYW1lcyhzbV9tKSA8LSBjKCJtZXRob2QiLCAic2NvcmUiKQpgYGAKCmBgYHtyfQpnZ3Bsb3Qoc21fbSwgYWVzKHg9bWV0aG9kLCB5PXNjb3JlLAogICAgICAgICAgICAgICAgICAgZmlsbD1hcy5mYWN0b3Ioc2NvcmUpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKwogIHRoZW1lX2xpZ2h0KCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAnTnVtYmVyIG9mIE1ldGhvZHMnLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShjKHZpcmlkaXMoMTQpKSwgMSkpICsKICB4bGFiKCJQcmltYXJ5IE1ldGhvZCIpICsKICB5bGFiKCJDb3VudCBvZiBWaXJhbCBDb250aWdzIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgoKCiMgUk9DIApgYGB7cn0KbGlicmFyeShwUk9DKQpgYGAKCmBgYHtyfQp2aXJ1c2VzJHRydWVwb3NpdGl2ZSA8LSByZXAoMCwgbnJvdyh2aXJ1c2VzKSkKdmlydXNlcyR0cnVlcG9zaXRpdmVbdmlydXNlcyRzZXF0eXBlPT0idmlydXMiXSA8LSAxCmBgYAoKCmBgYHtyfQpyb2NvYmogPC0gcm9jKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmUpCnJvY29ial9hbGwgPC0gcm9jKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsKQphdWMgPC0gcm91bmQoYXVjKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmUpLDQpCmF1Y19hbGwgPC0gcm91bmQoYXVjKHZpcnVzZXMkdHJ1ZXBvc2l0aXZlLCB2aXJ1c2VzJGtlZXBfc2NvcmVfYWxsKSw0KQojY3JlYXRlIFJPQyBwbG90Cmdncm9jKHJvY29iaiwgY29sb3VyID0gJ3N0ZWVsYmx1ZScsIHNpemUgPSAyKSArCiAgZ2d0aXRsZShwYXN0ZTAoJ1JPQyBDdXJ2ZSAnLCAnKEFVQyA9ICcsIGF1YywgJyknKSkgKwogIGNvb3JkX2VxdWFsKCkKZ2dyb2Mocm9jb2JqX2FsbCwgY29sb3VyID0gJ2dyZWVuJywgc2l6ZSA9IDIpICsKICBnZ3RpdGxlKHBhc3RlMCgnUk9DIEN1cnZlICcsICcoQVVDID0gJywgYXVjX2FsbCwgJyknKSkKYGBgClNlbnNpdGl2aXR5OiBUaGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgbW9kZWwgcHJlZGljdHMgYSBwb3NpdGl2ZSBvdXRjb21lIGZvciBhbiBvYnNlcnZhdGlvbiB3aGVuIGluZGVlZCB0aGUgb3V0Y29tZSBpcyBwb3NpdGl2ZS4KU3BlY2lmaWNpdHk6IFRoZSBwcm9iYWJpbGl0eSB0aGF0IHRoZSBtb2RlbCBwcmVkaWN0cyBhIG5lZ2F0aXZlIG91dGNvbWUgZm9yIGFuIG9ic2VydmF0aW9uIHdoZW4gaW5kZWVkIHRoZSBvdXRjb21lIGlzIG5lZ2F0aXZlLgoKCgoKIyBDb21wYXJpbmcgYmVoYXZpb3Igb2YgYWxsIHRlc3Rpbmcgc2V0cyBjb21iaW5lZCAoY2x1c3RlcmluZyBhbmFseXNlcykKCmBgYHtyfQp2aXJhbF9zY29yZXMgPC0gbWF0cml4KGRhdGE9MCwgbnJvdz1ucm93KHZpcnVzZXMpLCBuY29sPW5yb3coY29tYm9zX2xpc3QpKQpudW1fdmlydXNlcyA8LSBkYXRhLmZyYW1lKHRvb2xjb21ibz1yZXAoMCwgbnJvdyhjb21ib3NfbGlzdCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV92aXJ1c2VzPXJlcCgwLCBucm93KGNvbWJvc19saXN0KSkpCgpmb3IgKGkgaW4gMTpucm93KGNvbWJvc19saXN0KSkgewogIHZpcmFsX3Njb3Jlc1ssaV0gPC0gZ2V0dGluZ192aXJhbF9zZXRfMSh2aXJ1c2VzLCBpbmNsdWRlX3ZpYnJhbnQgPSBjb21ib3NfbGlzdCRWSUJSQU5UW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdmlyc29ydGVyID0gY29tYm9zX2xpc3QkVlNbaV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5jbHVkZV92aXJzb3J0ZXIyID0gY29tYm9zX2xpc3QkVlMyW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfdHVuaW5nID0gY29tYm9zX2xpc3QkQ2hlY2tWW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfa2FpanUgPSBjb21ib3NfbGlzdCRLYWlqdVtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlX2RlZXB2aXJmaW5kZXIgPSBjb21ib3NfbGlzdCREVkZbaV0pCiAgCiAgbnVtX3ZpcnVzZXMkbnVtX3ZpcnVzZXNbaV0gPC0gdGFibGUodmlyYWxfc2NvcmVzWyxpXT49MSlbWzJdXQogIAogIG51bV92aXJ1c2VzJHRvb2xjb21ib1tpXSA8LSBjb21ib3NfbGlzdCR0b29sY29tYm9baV0KICAKICBudW1fdmlydXNlcyR0b29sY29tYm8yW2ldIDwtIGNvbWJvc19saXN0JHRvb2xjb21ibzJbaV0KfQoKbnVtX3ZpcnVzZXMkbnVtcnVsZXMgPC0gc3RyX2NvdW50KG51bV92aXJ1c2VzJHRvb2xjb21ibywgIjEiKQpudW1fdmlydXNlcyA8LSBudW1fdmlydXNlc1tvcmRlcihudW1fdmlydXNlcyRudW1fdmlydXNlcywgZGVjcmVhc2luZz1GKSxdCm51bV92aXJ1c2VzJHRvb2xjb21ibyA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvLCBsZXZlbHMgPSB1bmlxdWUobnVtX3ZpcnVzZXMkdG9vbGNvbWJvKSkKbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiA8LSBmYWN0b3IobnVtX3ZpcnVzZXMkdG9vbGNvbWJvMiwgbGV2ZWxzID0gdW5pcXVlKG51bV92aXJ1c2VzJHRvb2xjb21ibzIpKQpudW1fdmlydXNlcyRudW1ydWxlcyA8LSBhcy5mYWN0b3IobnVtX3ZpcnVzZXMkbnVtcnVsZXMpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PXRvb2xjb21ibywgeT1udW1fdmlydXNlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1udW1ydWxlcywgZmlsbD1udW1ydWxlcykpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCwgYW5nbGUgPSA5MCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJUb29sIENvbWJpbmF0aW9uIChDViwgRFZGLCBLSiwgVkIsIFZTLCBWUzIpIikgKwogIHlsYWIoIk51bSBWaXJ1c2VzIFByZWRpY3RlZCIpCgpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PXRvb2xjb21ibzIsIHk9bnVtX3ZpcnVzZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGFuZ2xlID0gOTApLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiVG9vbCBDb21iaW5hdGlvbiAoQ1YsIERWRiwgS0osIFZCLCBWUywgVlMyKSIpICsKICB5bGFiKCJOdW0gVmlydXNlcyBQcmVkaWN0ZWQiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QobnVtX3ZpcnVzZXMsIGFlcyh4PW51bXJ1bGVzLCB5PW51bV92aXJ1c2VzKSkgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3I9bnVtcnVsZXMpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9bnVtcnVsZXMsIGZpbGw9bnVtcnVsZXMpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBhbmdsZSA9IDkwKSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBUb29scyIpICsKICB5bGFiKCJOdW0gVmlydXNlcyBQcmVkaWN0ZWQiKQpgYGAKCgpgYGB7cn0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzW3Jvd1N1bXModmlyYWxfc2NvcmVzKT4wLF0KdmlyYWxfc2NvcmVzX25vemVyb3MgPC0gdmlyYWxfc2NvcmVzX25vemVyb3MgKyAxCnZpcmFsX3Njb3Jlc19ub3plcm9zIDwtIGFzLmRhdGEuZnJhbWUodmlyYWxfc2NvcmVzX25vemVyb3MpCgpjb2xuYW1lcyh2aXJhbF9zY29yZXNfbm96ZXJvcykgPC0gbnVtX3ZpcnVzZXMkdG9vbGNvbWJvMgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHBoeWxvc2VxKQpgYGAKCgpgYGB7cn0KdG9vbGRhdGEgPC0gbnVtX3ZpcnVzZXMKCnJvd25hbWVzKHRvb2xkYXRhKSA8LSB0b29sZGF0YSR0b29sY29tYm8yCmBgYAoKYGBge3J9CnBoeXNlcV9wb29sZWQgPC0gcGh5bG9zZXEob3R1X3RhYmxlKHZpcmFsX3Njb3Jlc19ub3plcm9zLCB0YXhhX2FyZV9yb3dzID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfZGF0YSh0b29sZGF0YSkpCmBgYAoKYGBge3J9Cm9yZGluYXRpb24gPC0gcGh5bG9zZXE6Om9yZGluYXRlKHBoeXNlcSA9cGh5c2VxX3Bvb2xlZCwgbWV0aG9kID0gIlBDb0EiLCBkaXN0YW5jZSA9ICJicmF5IikKcGh5bG9zZXE6OnBsb3Rfb3JkaW5hdGlvbihwaHlzZXEgPSBwaHlzZXFfcG9vbGVkLCBvcmRpbmF0aW9uID0gb3JkaW5hdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZT0ibnVtcnVsZXMiLCBjb2xvcj0ibnVtX3ZpcnVzZXMiKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsKICB0aGVtZV9idygpICsKICBnZW9tX2xhYmVsKGxhYmVsPXRvb2xkYXRhJHRvb2xjb21ibykKCnBoeWxvc2VxOjpwbG90X29yZGluYXRpb24ocGh5c2VxID0gcGh5c2VxX3Bvb2xlZCwgb3JkaW5hdGlvbiA9IG9yZGluYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGU9Im51bXJ1bGVzIiwgY29sb3I9Im51bV92aXJ1c2VzIikgKyAKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgdGhlbWVfYncoKQpgYGAKdG8gZG86IHRyeSBjb2xvcmluZyBhYm92ZSBiYXNlZCBvbiB0aGUgRjEgc2NvcmVzIG9mIHRoZSB0ZXN0aW5nIHNldCBvbiBlYWNoIGNvbWJpbmF0aW9uCgpgYGB7cn0KYnJheV9kaXN0IDwtIHBoeWxvc2VxOjpkaXN0YW5jZShwaHlzZXFfcG9vbGVkLCBtZXRob2Q9ImJyYXkiKQpjbHVzdGVycyA8LSBoY2x1c3QoZGlzdChicmF5X2Rpc3QpKQpwbG90KGNsdXN0ZXJzKQoKbXljbHVzdGVycyA8LSBjdXRyZWUoY2x1c3RlcnMsIGg9MS4xKQpgYGAKCgpgYGB7cn0KbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0xXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0yXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT0zXSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT00XSkKbmFtZXMobXljbHVzdGVyc1tteWNsdXN0ZXJzPT01XSkKCm15Y2x1c3RlcnNfZGYgPC0gdGliYmxlKGNvbWJvPW5hbWVzKG15Y2x1c3RlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9pbmRleD1teWNsdXN0ZXJzKQoKbXljbHVzdGVyc19kZiA8LSBzZXBhcmF0ZShteWNsdXN0ZXJzX2RmLCBjb2w9Y29tYm8sIGludG89YygiQ2hlY2tWIiwgIkRWRiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJLYWlqdSIsICJWSUJSQU5UIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlZpclNvcnRlciIsICJWaXJTb3J0ZXIyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIgIiwgcmVtb3ZlID0gRikKCgp0b29sX2NvdW50IDwtIGFzLmRhdGEuZnJhbWUocmJpbmQodGFibGUobXljbHVzdGVyc19kZiRDaGVja1YsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkRFZGLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJEthaWp1LCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJFZJQlJBTlQsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHRhYmxlKG15Y2x1c3RlcnNfZGYkVmlyU29ydGVyLCBteWNsdXN0ZXJzX2RmJGNsdXN0ZXJfaW5kZXgpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJsZShteWNsdXN0ZXJzX2RmJFZpclNvcnRlcjIsIG15Y2x1c3RlcnNfZGYkY2x1c3Rlcl9pbmRleClbMixdKQogICAgICAgICAgICAgICAgICAgICkKCnRvb2xfY291bnQkbWV0aG9kIDwtIGMoIkNoZWNrViIsICJEVkYiLCAiS2FpanUiLCAiVklCUkFOVCIsICJWaXJTb3J0ZXIiLCAiVmlyU29ydGVyMiIpCgp0b29sX2NvdW50IDwtIG1lbHQodG9vbF9jb3VudCkKCmNvbG5hbWVzKHRvb2xfY291bnQpIDwtIGMoInRvb2wiLCAiY2x1c3Rlcl9pbmRleCIsICJ0b29sX2NvdW50IikKYGBgCgpgYGB7cn0KcGFsIDwtIGdndGhlbWVzOjp0YWJsZWF1X2NvbG9yX3BhbChwYWxldHRlPSJUYWJsZWF1IDEwIiwgdHlwZT0icmVndWxhciIpCgpnZ3Bsb3QodG9vbF9jb3VudCwgYWVzKHg9Y2x1c3Rlcl9pbmRleCwgeT10b29sX2NvdW50LAogICAgICAgICAgICAgICAgICAgZmlsbD1jbHVzdGVyX2luZGV4LAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2x1c3Rlcl9pbmRleCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDAuNSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg2KSksIDEpKSArCiAgeGxhYigiQ2x1c3RlciIpICsKICB5bGFiKCJOdW1iZXIgb2YgVGltZXMgaW4gQ2x1c3RlciIpICsgCiAgZmFjZXRfd3JhcCh+dG9vbCwgc2NhbGVzID0gImZyZWUiKQpgYGAKCgpgYGB7cn0KIGdncGxvdCh2aXJ1c2VzLCBhZXMoeD1jaGVja3ZfdmlyYWxfZ2VuZXMsIHk9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMC41KSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDEpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHhsYWIoIk51bWJlciBvZiBWaXJhbCBTZXF1ZW5jZXMiKSArCiAgeWxhYigiIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCiBnZ3Bsb3QodmlydXNlcywgYWVzKHg9cGVyY2VudF92aXJhbCwgeT1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbikpICsKICBnZW9tX2JveHBsb3QoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiUGVyY2VudCBHZW5lcyBWaXJhbCIpICsKICB5bGFiKCIiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQoKIGdncGxvdCh2aXJ1c2VzLCBhZXMoeD1oYWxsbWFyaywgeT1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgZmlsbD1jb25mdXNpb25fbWF0cml4X2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbikpICsKICBnZW9tX2JveHBsb3QoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAwLjUpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmYWxzZSBuZWdhdGl2ZSIsICJmYWxzZSBwb3NpdGl2ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVlIG5lZ2F0aXZlIiwgInRydWUgcG9zaXRpdmUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBhbHBoYShyZXYocGFsKDQpKSwgMSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIiIpICsgCiAgZmFjZXRfd3JhcCh+c2VxdHlwZSwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCiAKZ2dwbG90KHZpcnVzZXMsIGFlcyh4PWhhbGxtYXJrLCB5PWNoZWNrdl92aXJhbF9nZW5lcywKICAgICAgICAgICAgICAgICAgIGZpbGw9Y29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIGNvbG9yPWNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb24pKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwKICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gYWxwaGEocmV2KHBhbCg0KSksIDAuNSksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoImZhbHNlIG5lZ2F0aXZlIiwgImZhbHNlIHBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRydWUgbmVnYXRpdmUiLCAidHJ1ZSBwb3NpdGl2ZSIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKHJldihwYWwoNCkpLCAxKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiZmFsc2UgbmVnYXRpdmUiLCAiZmFsc2UgcG9zaXRpdmUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHJ1ZSBuZWdhdGl2ZSIsICJ0cnVlIHBvc2l0aXZlIikpICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiTnVtYmVyIG9mIFZpcmFsIEdlbmVzIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpgYGB7cn0KdmlydXNlc19mYWxzZV9wb3NpdGl2ZSA8LSB2aXJ1c2VzW3ZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbj09ImZhbHNlIHBvc2l0aXZlIixdCnZpcnVzZXNfZmFsc2VfbmVnYXRpdmUgPC0gdmlydXNlc1t2aXJ1c2VzJGNvbmZ1c2lvbl9tYXRyaXhfaGlnaF9wcmVjaXNpb249PSJmYWxzZSBuZWdhdGl2ZSIsXQpgYGAKCmBgYHtyfQpnZ3Bsb3QodmlydXNlcywgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgZmlsbD1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIHNoYXBlPWNoZWNrdl9wcm92aXJ1cykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiTnVtYmVyIG9mIFZpcmFsIEdlbmVzIikgKyAKICBmYWNldF93cmFwKH5zZXF0eXBlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofnNlcXR5cGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQoKZ2dwbG90KHZpcnVzZXNfZmFsc2VfcG9zaXRpdmVbdmlydXNlc19mYWxzZV9wb3NpdGl2ZSRzZXF0eXBlPT0iYmFjdGVyaWEiXSwgYWVzKHg9aGFsbG1hcmssIHk9Y2hlY2t2X2xlbmd0aCwKICAgICAgICAgICAgICAgICAgIGZpbGw9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgY29sb3I9Y2hlY2t2X3ZpcmFsX2dlbmVzLAogICAgICAgICAgICAgICAgICAgc2hhcGU9Y2hlY2t2X3Byb3ZpcnVzKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC4zKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpLAogICkgKwogIHhsYWIoIk51bWJlciBvZiBIYWxsbWFyayBHZW5lcyIpICsKICB5bGFiKCJDb250aWcgTGVuZ3RoIikgKyAKICBmYWNldF93cmFwKH5LYWlqdV9WaXJhbCwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpnZ3Bsb3QodmlydXNlc19mYWxzZV9wb3NpdGl2ZVt2aXJ1c2VzX2ZhbHNlX3Bvc2l0aXZlJHNlcXR5cGU9PSJmdW5naSJdLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1rZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgY29sb3I9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIHNoYXBlPWNoZWNrdl9wcm92aXJ1cykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiQ29udGlnIExlbmd0aCIpICsgCiAgZmFjZXRfd3JhcCh+S2FpanVfVmlyYWwsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQoKZ2dwbG90KHZpcnVzZXNfZmFsc2VfcG9zaXRpdmVbdmlydXNlc19mYWxzZV9wb3NpdGl2ZSRzZXF0eXBlPT0icHJvdGlzdCJdLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX25lZ2F0aXZlLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBjb2xvcj1jaGVja3ZfdmlyYWxfZ2VuZXMsCiAgICAgICAgICAgICAgICAgICBzaGFwZT1jaGVja3ZfcHJvdmlydXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjMpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNiksCiAgKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhbGxtYXJrIEdlbmVzIikgKwogIHlsYWIoIkNvbnRpZyBMZW5ndGgiKSArIAogIGZhY2V0X3dyYXAofkthaWp1X1ZpcmFsLCBzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKCmdncGxvdCh2aXJ1c2VzX2ZhbHNlX25lZ2F0aXZlLCBhZXMoeD1oYWxsbWFyaywgeT1jaGVja3ZfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgZmlsbD1rZWVwX3Njb3JlX2hpZ2hfcHJlY2lzaW9uLAogICAgICAgICAgICAgICAgICAgY29sb3I9a2VlcF9zY29yZV9oaWdoX3ByZWNpc2lvbiwKICAgICAgICAgICAgICAgICAgIHNoYXBlPWNoZWNrdl9wcm92aXJ1cykpICsKICBnZW9tX3BvaW50KGFscGhhPTAuMykgKwogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgICBsZWdlbmQudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE2KSwKICApICsKICB4bGFiKCJOdW1iZXIgb2YgSGFsbG1hcmsgR2VuZXMiKSArCiAgeWxhYigiQ29udGlnIExlbmd0aCIpICsgCiAgZmFjZXRfd3JhcCh+S2FpanVfVmlyYWwsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgoKCmBgYHtyfQp0YWJsZSh2aXJ1c2VzJGhhbGxtYXJrW3ZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbj09ImZhbHNlIHBvc2l0aXZlIl0+MCkKCnRhYmxlKHZpcnVzZXMkcGVyY2VudF9ob3N0W3ZpcnVzZXMkY29uZnVzaW9uX21hdHJpeF9oaWdoX3ByZWNpc2lvbj09ImZhbHNlIHBvc2l0aXZlIl08NTApCmBgYAoKCiMjIFZpc3VhbGl6aW5nIGNvbmZ1c2lvbiBtYXRyaXggYnkgbnVtYmVyIG9mIHRvb2xzCgpgYGB7cn0KY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUgPC0gdmlydXNlcyAlPiUgCiAgc2VsZWN0KGNvbnRhaW5zKCJrZWVwX3Njb3JlIiksIHNlcXR5cGUsIEluZGV4KSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1jb250YWlucygia2VlcF9zY29yZSIpLCAKICAgICAgICAgICAgICAgbmFtZXNfdG89InBpcGVsaW5lX3R5cGUiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG89InBpcGVsaW5lX3ZhbHVlIikgJT4lCiAgbXV0YXRlKHBpcGVsaW5lX3R5cGU9c3ViKCJrZWVwX3Njb3JlXyIsICIiLCBwaXBlbGluZV90eXBlKSkgJT4lCiAgY291bnQoc2VxdHlwZSwgSW5kZXgsIHBpcGVsaW5lX3R5cGUsIHBpcGVsaW5lX3ZhbHVlKQpgYGAKCmBgYHtyfQpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRjb25mdXNpb25fbWF0cml4IDwtICJ0cnVlIG5lZ2F0aXZlIgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRjb25mdXNpb25fbWF0cml4W2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHNlcXR5cGU9PSJ2aXJ1cyIgJiBjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZTwxXSA8LSAiZmFsc2UgbmVnYXRpdmUiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGNvbmZ1c2lvbl9tYXRyaXhbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkc2VxdHlwZT09InZpcnVzIiAmIGNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPj0xXSA8LSAidHJ1ZSBwb3NpdGl2ZSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkY29uZnVzaW9uX21hdHJpeFtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRzZXF0eXBlIT0idmlydXMiICYgY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU+PTFdIDwtICJmYWxzZSBwb3NpdGl2ZSIKYGBgCgpgYGB7cn0KY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemUgPC0gY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWUKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemVbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU+MV0gPC0gIj4gMSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemVbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU9PTFdIDwtICIxIgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZT09MC41XSA8LSAiMC41Igpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRrZWVwX3Njb3JlX3Zpc3VhbGl6ZVtjb25mdXNpb25fYnlfa2VlcF9zY29yZSRwaXBlbGluZV92YWx1ZT09MF0gPC0gIjAiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplW2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPT0tMC41XSA8LSAiLTAuNSIKY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemVbY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUkcGlwZWxpbmVfdmFsdWU9PS0xXSA8LSAiLTEiCmNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJGtlZXBfc2NvcmVfdmlzdWFsaXplW2NvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlJHBpcGVsaW5lX3ZhbHVlPD0tMV0gPC0gIjwgLTEiCgpjb25mdXNpb25fYnlfa2VlcF9zY29yZSRrZWVwX3Njb3JlX3Zpc3VhbGl6ZSA8LSBmYWN0b3IoY29uZnVzaW9uX2J5X2tlZXBfc2NvcmUka2VlcF9zY29yZV92aXN1YWxpemUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiPCAtMSIsICItMSIsICItMC41IiwgIjAiLCAiMC41IiwiMSIsICI+IDEiKSkKYGBgCgpgYGB7cn0KZ2dwbG90KGNvbmZ1c2lvbl9ieV9rZWVwX3Njb3JlLCBhZXMoeD1jb25mdXNpb25fbWF0cml4LCB5PW4sCiAgICAgICAgICAgICAgICAgICBmaWxsPWtlZXBfc2NvcmVfdmlzdWFsaXplLCBjb2xvcj1rZWVwX3Njb3JlX3Zpc3VhbGl6ZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNikKICApICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICdWaXJhbCBTY29yZScsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKGModmlyaWRpcyg2KSksIDEpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICdWaXJhbCBTY29yZScsCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGFscGhhKGModmlyaWRpcyg2KSksIDAuNSkpICsKICB4bGFiKCJJbmRleCIpICsKICB5bGFiKCJTZXF1ZW5jZSBDb3VudCIpICsKICBmYWNldF93cmFwKH5waXBlbGluZV90eXBlLCBzY2FsZXMgPSAiZnJlZSIpCgpgYGAK